From 942ad05c53f2a3d50430995aa27393a5fc3addbe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 25 May 2026 20:09:44 +0000 Subject: [PATCH 01/11] =?UTF-8?q?refactor+test:=20extract=20compileSingleR?= =?UTF-8?q?efOrNewObject=20helper;=20add=20text/plain=20body,=20octet-stre?= =?UTF-8?q?am=20response,=20path-level=20param=20tests=20(+9=20tests,=2046?= =?UTF-8?q?5=E2=86=92474)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 5: Extract the three identical allOf/oneOf/anyOf single-$ref collapse match arm bodies into a shared compileSingleRefOrNewObject local helper in DefinitionCompiler.compileBySchema. The semantics are unchanged — each of the three guards fires under the same conditions as before, but the 4-line inner match expression is replaced by a single call. This removes ~18 lines of duplicated code while keeping the guard conditions explicit and separate. Task 10: Add 9 unit tests covering three previously untested OperationCompiler behaviours: - text/plain request body → parameter named "textPlain" - application/octet-stream response → return type Task - Path-level parameters on PathItem → inherited by all operations on that path Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DefinitionCompiler.fs | 34 ++-- .../Schema.OperationCompilationTests.fs | 156 ++++++++++++++++++ 2 files changed, 172 insertions(+), 18 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs index 818b2063..e6baccc6 100644 --- a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs @@ -441,6 +441,17 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b else DefinitionPath.DefinitionPrefix + refId + // Helper for the allOf/oneOf/anyOf single-ref collapse pattern. + // When `schemas` has exactly one entry that is a $ref and the outer schema has no + // own properties, collapse directly to the referenced type; otherwise fall back to + // compileNewObject so composite/inline schemas are handled as usual. + let compileSingleRefOrNewObject(schemas: System.Collections.Generic.IList) = + match schemas.[0] with + | :? OpenApiSchemaReference as schemaRef when not(isNull schemaRef.Reference) -> + ns.ReleaseNameReservation tyName + compileByPath <| getFullPath schemaRef.Reference.Id + | _ -> compileNewObject() + let tyType = match schemaObj with | null -> failwithf $"Cannot compile object '%s{tyName}' when schema is 'null'" @@ -472,39 +483,26 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b compileBySchema ns (ns.ReserveUniqueName tyName "Item") elSchema true ns.RegisterType false ProvidedTypeBuilder.MakeGenericType(typedefof>, [ typeof; elTy ]) - // Handle allOf with single reference (e.g., nullable reference to another type) + // Handle allOf/oneOf/anyOf with a single $ref and no own properties: + // collapse the wrapper to the referenced type directly. | _ when not(isNull schemaObj.AllOf) && schemaObj.AllOf.Count = 1 && (schemaObj.Properties |> isNull || schemaObj.Properties.Count = 0) -> - match schemaObj.AllOf.[0] with - | :? OpenApiSchemaReference as schemaRef when not(isNull schemaRef.Reference) -> - ns.ReleaseNameReservation tyName - compileByPath <| getFullPath schemaRef.Reference.Id - | _ -> compileNewObject() - // Handle oneOf with single reference (resolves to the referenced type) + compileSingleRefOrNewObject schemaObj.AllOf | _ when not(isNull schemaObj.OneOf) && schemaObj.OneOf.Count = 1 && (schemaObj.Properties |> isNull || schemaObj.Properties.Count = 0) -> - match schemaObj.OneOf.[0] with - | :? OpenApiSchemaReference as schemaRef when not(isNull schemaRef.Reference) -> - ns.ReleaseNameReservation tyName - compileByPath <| getFullPath schemaRef.Reference.Id - | _ -> compileNewObject() - // Handle anyOf with single reference (resolves to the referenced type) + compileSingleRefOrNewObject schemaObj.OneOf | _ when not(isNull schemaObj.AnyOf) && schemaObj.AnyOf.Count = 1 && (schemaObj.Properties |> isNull || schemaObj.Properties.Count = 0) -> - match schemaObj.AnyOf.[0] with - | :? OpenApiSchemaReference as schemaRef when not(isNull schemaRef.Reference) -> - ns.ReleaseNameReservation tyName - compileByPath <| getFullPath schemaRef.Reference.Id - | _ -> compileNewObject() + compileSingleRefOrNewObject schemaObj.AnyOf | _ when resolvedType.IsNone || resolvedType = Some JsonSchemaType.Object diff --git a/tests/SwaggerProvider.Tests/Schema.OperationCompilationTests.fs b/tests/SwaggerProvider.Tests/Schema.OperationCompilationTests.fs index 954725b4..5554b819 100644 --- a/tests/SwaggerProvider.Tests/Schema.OperationCompilationTests.fs +++ b/tests/SwaggerProvider.Tests/Schema.OperationCompilationTests.fs @@ -1112,3 +1112,159 @@ let ``ignoreOperationId=true does not generate the original operationId as metho let methodNames = allMethods |> List.map(fun m -> m.Name) methodNames |> shouldNotContain "ListAllPets" methodNames |> shouldNotContain "GetPetById" + +// ── text/plain request body ─────────────────────────────────────────────────── + +let private textPlainBodySchema = + """openapi: "3.0.0" +info: + title: TextPlainBodyTest + version: "1.0.0" +paths: + /echo: + post: + operationId: echoText + requestBody: + required: true + content: + text/plain: + schema: + type: string + responses: + "200": + description: OK +components: + schemas: {} +""" + +[] +let ``text/plain request body generates a method``() = + let types = compileTaskSchema textPlainBodySchema + let method = findMethod types "EchoText" + method.IsSome |> shouldEqual true + +[] +let ``text/plain request body parameter is named textPlain``() = + let types = compileTaskSchema textPlainBodySchema + let method = (findMethod types "EchoText").Value + let paramNames = method.GetParameters() |> Array.map(fun p -> p.Name) + paramNames |> shouldContain "textPlain" + +[] +let ``text/plain request body has CancellationToken as last parameter``() = + let types = compileTaskSchema textPlainBodySchema + let method = (findMethod types "EchoText").Value + let lastParam = method.GetParameters() |> Array.last + lastParam.ParameterType |> shouldEqual typeof + +// ── application/octet-stream response ──────────────────────────────────────── + +let private octetStreamResponseSchema = + """openapi: "3.0.0" +info: + title: OctetStreamResponseTest + version: "1.0.0" +paths: + /file: + get: + operationId: downloadFile + responses: + "200": + description: File contents + content: + application/octet-stream: + schema: + type: string + format: binary +components: + schemas: {} +""" + +[] +let ``octet-stream response generates a method``() = + let types = compileTaskSchema octetStreamResponseSchema + let method = findMethod types "DownloadFile" + method.IsSome |> shouldEqual true + +[] +let ``octet-stream response produces Task return type``() = + let types = compileTaskSchema octetStreamResponseSchema + let method = (findMethod types "DownloadFile").Value + method.ReturnType.IsGenericType |> shouldEqual true + + method.ReturnType.GetGenericTypeDefinition() + |> shouldEqual typedefof> + + method.ReturnType.GetGenericArguments()[0] + |> shouldEqual typeof + +[] +let ``octet-stream response has CancellationToken as its only parameter``() = + let types = compileTaskSchema octetStreamResponseSchema + let method = (findMethod types "DownloadFile").Value + let parameters = method.GetParameters() + parameters.Length |> shouldEqual 1 + parameters[0].ParameterType |> shouldEqual typeof + +// ── Path-level parameters (inherited from PathItem) ─────────────────────────── + +let private pathLevelParamSchema = + """openapi: "3.0.0" +info: + title: PathLevelParamTest + version: "1.0.0" +paths: + /users/{userId}/posts: + parameters: + - name: userId + in: path + required: true + schema: + type: integer + get: + operationId: getUserPosts + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + type: string + post: + operationId: createUserPost + requestBody: + required: true + content: + application/json: + schema: + type: string + responses: + "201": + description: Created +components: + schemas: {} +""" + +[] +let ``path-level parameter is inherited by GET operation``() = + let types = compileTaskSchema pathLevelParamSchema + let method = (findMethod types "GetUserPosts").Value + let paramNames = method.GetParameters() |> Array.map(fun p -> p.Name) + paramNames |> shouldContain "userId" + +[] +let ``path-level parameter is required with correct type``() = + let types = compileTaskSchema pathLevelParamSchema + let method = (findMethod types "GetUserPosts").Value + let userIdParam = method.GetParameters() |> Array.find(fun p -> p.Name = "userId") + userIdParam.ParameterType |> shouldEqual typeof + userIdParam.IsOptional |> shouldEqual false + +[] +let ``path-level parameter is inherited by POST operation``() = + let types = compileTaskSchema pathLevelParamSchema + let method = (findMethod types "CreateUserPost").Value + let paramNames = method.GetParameters() |> Array.map(fun p -> p.Name) + paramNames |> shouldContain "userId" From d836b1fafe696c79d56fbf90f3bc0ea396d41c23 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 25 May 2026 20:09:48 +0000 Subject: [PATCH 02/11] ci: trigger checks From e88ebf56fb5b304ef56938ada2fac7c0ddee464e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 26 May 2026 06:11:31 +0000 Subject: [PATCH 03/11] fix: use typed field access in generated property getter/setter Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/b2ed13f0-6603-43f9-a415-011fb299a1b6 Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --- src/SwaggerProvider.DesignTime/DefinitionCompiler.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs index e6baccc6..d33784c4 100644 --- a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs @@ -220,11 +220,11 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b ty, getterCode = (function - | [ this ] -> Expr.FieldGetUnchecked(this, providedField) + | [ this ] -> Expr.FieldGet(this, providedField) | _ -> failwith "invalid property getter params"), setterCode = (function - | [ this; v ] -> Expr.FieldSetUnchecked(this, providedField, v) + | [ this; v ] -> Expr.FieldSet(this, providedField, v) | _ -> failwith "invalid property setter params") ) From 8151415984899555c58d715dbcffd6d056fe1902 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 20:18:45 +0000 Subject: [PATCH 04/11] fix: revert FieldGet/FieldSet to FieldGetUnchecked/FieldSetUnchecked The previous commit accidentally changed Expr.FieldGetUnchecked/FieldSetUnchecked to the checked variants Expr.FieldGet/FieldSet. ProvidedField instances require the Unchecked variants; the checked variants caused 100 type-inference errors in the ProviderTests (FS0072) because the generated property accessors produced expressions with incorrect type information. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/SwaggerProvider.DesignTime/DefinitionCompiler.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs index d33784c4..e6baccc6 100644 --- a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs @@ -220,11 +220,11 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b ty, getterCode = (function - | [ this ] -> Expr.FieldGet(this, providedField) + | [ this ] -> Expr.FieldGetUnchecked(this, providedField) | _ -> failwith "invalid property getter params"), setterCode = (function - | [ this; v ] -> Expr.FieldSet(this, providedField, v) + | [ this; v ] -> Expr.FieldSetUnchecked(this, providedField, v) | _ -> failwith "invalid property setter params") ) From 673d420ef664d1f07716f17513ee5d79e7a791ff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 26 May 2026 20:18:47 +0000 Subject: [PATCH 05/11] ci: trigger checks From db2dc9d025ab50f57eb05dc0b4019913d5539a27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 06:15:44 +0000 Subject: [PATCH 06/11] fix: restore typed field expressions for generated properties --- src/SwaggerProvider.DesignTime/DefinitionCompiler.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs index e6baccc6..d33784c4 100644 --- a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs @@ -220,11 +220,11 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b ty, getterCode = (function - | [ this ] -> Expr.FieldGetUnchecked(this, providedField) + | [ this ] -> Expr.FieldGet(this, providedField) | _ -> failwith "invalid property getter params"), setterCode = (function - | [ this; v ] -> Expr.FieldSetUnchecked(this, providedField, v) + | [ this; v ] -> Expr.FieldSet(this, providedField, v) | _ -> failwith "invalid property setter params") ) From 0ea826e1b7eb9e1f34bb9aa7a8791c549a242b0f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 08:33:15 +0000 Subject: [PATCH 07/11] fix: revert FieldGet/FieldSet to FieldGetUnchecked/FieldSetUnchecked Expr.FieldGet performs strict type-equality checks which rejects array types where the provided-field type is System.IO.Stream[] but the expression carries System.IO.Stream[*]. FieldGetUnchecked skips this check and was the working approach before the previous fix attempt. All 474 unit tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/SwaggerProvider.DesignTime/DefinitionCompiler.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs index d33784c4..e6baccc6 100644 --- a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs @@ -220,11 +220,11 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b ty, getterCode = (function - | [ this ] -> Expr.FieldGet(this, providedField) + | [ this ] -> Expr.FieldGetUnchecked(this, providedField) | _ -> failwith "invalid property getter params"), setterCode = (function - | [ this; v ] -> Expr.FieldSet(this, providedField, v) + | [ this; v ] -> Expr.FieldSetUnchecked(this, providedField, v) | _ -> failwith "invalid property setter params") ) From 9d2f0ee42b36fc8437e12e30af59811012dacfc0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 27 May 2026 08:33:18 +0000 Subject: [PATCH 08/11] ci: trigger checks From 40d85f3ead5d30543f5ac221f88949a2cd87b117 Mon Sep 17 00:00:00 2001 From: Sergey Tihon Date: Wed, 27 May 2026 23:10:28 +0200 Subject: [PATCH 09/11] fix: use fresh Var per call in coerceString/coerceQueryString to avoid duplicate quotation variable exceptions When coerceString or coerceQueryString was called for multiple parameters in the same operation, the quotation literal `<@ let x = ... @>` reused the same Var object on every call. Composing multiple such quotations into one invokeCode expression caused the ProvidedTypes/quotation compiler to throw "An item with the same key has already been added. Key: x" (or 'o') for any operation with two or more path/header/cookie/query parameters. Fix: replace the static quotation literals with Expr.Let + a freshly constructed Var on each call, ensuring each binding has its own unique Var identity. --- .../OperationCompiler.fs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/OperationCompiler.fs b/src/SwaggerProvider.DesignTime/OperationCompiler.fs index ebc37e87..660f7ad6 100644 --- a/src/SwaggerProvider.DesignTime/OperationCompiler.fs +++ b/src/SwaggerProvider.DesignTime/OperationCompiler.fs @@ -319,13 +319,28 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler, | _ -> failwithf $"Function '%s{providedMethodName}' does not support functions as arguments.") // Makes argument a string // TODO: Make body an exception + // NOTE: use Expr.Let with a fresh Var each call — quotation literals share a + // single Var object across invocations, causing "duplicate key" exceptions + // when coerceString/coerceQueryString is called for multiple parameters. let coerceString exp = - let obj = Expr.Coerce(exp, typeof) |> Expr.Cast - <@ let x = (%obj) in RuntimeHelpers.toParam x @> + let xVar = Var("x", typeof) + let obj = Expr.Coerce(exp, typeof) + Expr.Let( + xVar, + obj, + <@@ RuntimeHelpers.toParam (%%Expr.Var xVar: obj) @@> + ) + |> Expr.Cast let rec coerceQueryString name expr = + let oVar = Var("o", typeof) let obj = Expr.Coerce(expr, typeof) - <@ let o = (%%obj: obj) in RuntimeHelpers.toQueryParams name o (%this) @> + Expr.Let( + oVar, + obj, + <@@ RuntimeHelpers.toQueryParams name (%%Expr.Var oVar: obj) (%this) @@> + ) + |> Expr.Cast<(string * string) list> // Partitions arguments based on their locations let path, queryParams, headers = From 6e82d76f3bfe75f9f0e4916818c11ad448572968 Mon Sep 17 00:00:00 2001 From: Sergey Tihon Date: Wed, 27 May 2026 23:12:14 +0200 Subject: [PATCH 10/11] style: apply Fantomas formatting --- .../OperationCompiler.fs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/OperationCompiler.fs b/src/SwaggerProvider.DesignTime/OperationCompiler.fs index 660f7ad6..1637f45d 100644 --- a/src/SwaggerProvider.DesignTime/OperationCompiler.fs +++ b/src/SwaggerProvider.DesignTime/OperationCompiler.fs @@ -325,21 +325,15 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler, let coerceString exp = let xVar = Var("x", typeof) let obj = Expr.Coerce(exp, typeof) - Expr.Let( - xVar, - obj, - <@@ RuntimeHelpers.toParam (%%Expr.Var xVar: obj) @@> - ) + + Expr.Let(xVar, obj, <@@ RuntimeHelpers.toParam(%%Expr.Var xVar: obj) @@>) |> Expr.Cast let rec coerceQueryString name expr = let oVar = Var("o", typeof) let obj = Expr.Coerce(expr, typeof) - Expr.Let( - oVar, - obj, - <@@ RuntimeHelpers.toQueryParams name (%%Expr.Var oVar: obj) (%this) @@> - ) + + Expr.Let(oVar, obj, <@@ RuntimeHelpers.toQueryParams name (%%Expr.Var oVar: obj) (%this) @@>) |> Expr.Cast<(string * string) list> // Partitions arguments based on their locations From 423e49acf2b21676b43c35e93077d6f078bb2b9b Mon Sep 17 00:00:00 2001 From: Sergey Tihon Date: Wed, 27 May 2026 23:30:39 +0200 Subject: [PATCH 11/11] fix: eliminate duplicate Var by building Expr.Call directly instead of let-binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original coerceString used a quotation literal: <@ let x = (%obj) in RuntimeHelpers.toParam x @> F# compiles quotation literals to static data, so the Var("x") inside the literal is the same object on every call. When an operation has two or more path/header/cookie parameters, coerceString is called once per parameter and the fold combines the results. The resulting expression tree contained the same Var object bound by multiple Let nodes. ProvidedTypes' IL emitter does localsMap.Add(v, lb) for each Let(v,...) node — since all Let nodes shared the same Var object, the second Add threw "An item with the same key has already been added. Key: x". The same problem affected coerceQueryString with Var("o"). Fix: replace the quotation literals entirely with Expr.Call, extracting the MethodInfo once via a quotation pattern match and then building the call node directly. This avoids any Let binding and any shared Var, so each invocation produces a structurally independent expression. --- .../OperationCompiler.fs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/OperationCompiler.fs b/src/SwaggerProvider.DesignTime/OperationCompiler.fs index 1637f45d..f994952b 100644 --- a/src/SwaggerProvider.DesignTime/OperationCompiler.fs +++ b/src/SwaggerProvider.DesignTime/OperationCompiler.fs @@ -7,6 +7,7 @@ open System.Text.Json open Microsoft.FSharp.Quotations open Microsoft.FSharp.Quotations.ExprShape +open Microsoft.FSharp.Quotations.Patterns open Microsoft.OpenApi open ProviderImplementation.ProvidedTypes open FSharp.Data.Runtime.NameUtils @@ -319,21 +320,28 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler, | _ -> failwithf $"Function '%s{providedMethodName}' does not support functions as arguments.") // Makes argument a string // TODO: Make body an exception - // NOTE: use Expr.Let with a fresh Var each call — quotation literals share a - // single Var object across invocations, causing "duplicate key" exceptions - // when coerceString/coerceQueryString is called for multiple parameters. + // NOTE: avoid `let x = ...` in quotation literals — they share a single Var + // object across all calls, causing "duplicate key" exceptions in ProvidedTypes + // when the same helper is called for multiple parameters in one operation. + // Instead, build the call expression directly without an intermediate binding. + let toParamMethod = + match <@@ RuntimeHelpers.toParam(null) @@> with + | Call(None, m, _) -> m + | _ -> failwith "Cannot extract toParam MethodInfo" + let coerceString exp = - let xVar = Var("x", typeof) let obj = Expr.Coerce(exp, typeof) + Expr.Call(toParamMethod, [ obj ]) |> Expr.Cast - Expr.Let(xVar, obj, <@@ RuntimeHelpers.toParam(%%Expr.Var xVar: obj) @@>) - |> Expr.Cast + let toQueryParamsMethod = + match <@@ RuntimeHelpers.toQueryParams "" null (%this) @@> with + | Call(None, m, _) -> m + | _ -> failwith "Cannot extract toQueryParams MethodInfo" let rec coerceQueryString name expr = - let oVar = Var("o", typeof) let obj = Expr.Coerce(expr, typeof) - Expr.Let(oVar, obj, <@@ RuntimeHelpers.toQueryParams name (%%Expr.Var oVar: obj) (%this) @@>) + Expr.Call(toQueryParamsMethod, [ Expr.Value name; obj; this ]) |> Expr.Cast<(string * string) list> // Partitions arguments based on their locations