Skip to content

[swift5] fix: annotate the query-parameter dictionary so large operations type-check - #24871

Open
wiebren wants to merge 1 commit into
OpenAPITools:masterfrom
wiebren:fix/swift5-annotate-parameter-dictionary
Open

[swift5] fix: annotate the query-parameter dictionary so large operations type-check#24871
wiebren wants to merge 1 commit into
OpenAPITools:masterfrom
wiebren:fix/swift5-annotate-parameter-dictionary

Conversation

@wiebren

@wiebren wiebren commented Sep 4, 2026

Copy link
Copy Markdown

Operations with many query parameters generate swift5 code that the compiler refuses to
type-check. Against a real production spec (listing endpoints with 9+ heterogeneous
optional query parameters), swift build fails 520 times with:

APIs/SslAPI.swift:372:9: error: the compiler is unable to type-check this expression in
reasonable time; try breaking up the expression into distinct sub-expressions
    localVariableUrlComponents?.queryItems = APIHelper.mapValuesToQueryItems([
        "filter": (wrappedValue: filter?.encodeToJSON(), isExplode: true),
        "order": (wrappedValue: order?.encodeToJSON(), isExplode: true),
        ... 9 entries ...
    ])

The cause

api.mustache passes the dictionary of (wrappedValue:, isExplode:) tuples to
APIHelper.mapValuesToQueryItems as one un-annotated literal. mapValuesToQueryItems is
overloaded and every entry's value goes through an encodeToJSON() call that is itself
resolved by overload, so the constraint solver has to consider every combination across
every entry at once. With enough entries of different parameter types (String?, Int?,
Bool?, AnyCodable?, …) it exceeds its budget and gives up. This is the standard Swift
expression-too-complex failure, and the standard remedy is to state the type instead of
making the solver infer it.

The fix

Bind the literal to an explicitly typed local before the call — the parameter type of
mapValuesToQueryItems spelled out at the call site:

let localVariableQueryParameters: [String: (wrappedValue: Any?, isExplode: Bool)] = [
    "filter": (wrappedValue: filter?.encodeToJSON(), isExplode: true),
    ...
]
localVariableUrlComponents?.queryItems = APIHelper.mapValuesToQueryItems(localVariableQueryParameters)

With the annotation each entry is checked independently against (wrappedValue: Any?, isExplode: Bool), so solver time is linear in the number of parameters. The same file
already uses this pattern two lines down: let localVariableNillableHeaders: [String: Any?] = [...]. Wire behavior is unchanged — the same overload of
mapValuesToQueryItems is selected as before, with the same values.

The swift6 generator's api.mustache builds the same un-annotated literal and the same
change applies there; happy to extend this PR or follow up, whichever you prefer.

Tests / verification

  • Swift5ClientCodegenTest#queryParameterDictionaryIsTypeAnnotatedTest generates the
    petstore PetAPI.swift and asserts the typed binding; it fails on master and passes
    with the fix. Full swift5 test classes pass.
  • Samples regenerated with ./bin/generate-samples.sh ./bin/configs/swift5-*.yaml; the
    diff is exactly the mechanical two-line reshaping of each query-parameter dictionary.
  • The regenerated urlsessionLibrary petstore sample builds cleanly (swift build in a
    swift:6.1 container), confirming the annotated form compiles for explode and
    non-explode, required and optional parameters. Demonstrating the 520-error timeout
    disappearing requires the large production spec; the annotation is the remedy swiftc
    itself suggests for this failure class.

Existing upstream issues: none found for the type-check timeout (searched
"reasonable time" swift, mapValuesToQueryItems). #12449 and #6906 concern the runtime
design of mapValuesToQueryItems, not compile-time blowup.

PR checklist

  • Read the contribution guidelines.
  • Built the project and updated samples (./bin/generate-samples.sh ./bin/configs/swift5-*.yaml; docs export unchanged).
  • Technical committee: @4brunu

Generated with Claude Code


Summary by cubic

Fixes swift5 generated code failing to type-check for operations with many query parameters.

  • Un-annotated dictionary literals made the constraint solver resolve every encodeToJSON() overload at once, timing out on 9+ heterogeneous parameters.
  • The template now binds the dictionary to an explicitly typed local variable, making solver time linear in the number of parameters.
  • Wire behavior is unchanged.
  • Adds a generator test asserting the typed binding; samples regenerated across all swift5 libraries.

Written for commit 1aadb75. Summary will update on new commits.

Review in cubic

… inferring it

api.mustache emitted the (wrappedValue:, isExplode:) tuple dictionary as an
un-annotated literal passed straight to APIHelper.mapValuesToQueryItems.
For operations with many heterogeneous query parameters the constraint
solver has to consider every encodeToJSON() overload for every entry at
once and gives up with 'the compiler is unable to type-check this
expression in reasonable time'. Bind the literal to a local with the
parameter type of mapValuesToQueryItems spelled out, the same way the
localVariableNillableHeaders literal is already annotated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CxDNCjqJycKTfzVWg2SeTJ

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 29 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/main/resources/swift5/api.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/swift5/api.mustache:432">
P2: When an operation has a query parameter named `localVariableQueryParameters`, this declaration collides with the generated method parameter and makes the Swift client fail to compile. Generate a collision-free local name or scope the typed dictionary separately.</violation>

<violation number="2" location="modules/openapi-generator/src/main/resources/swift5/api.mustache:437">
P3: The PR description states the typed-dictionary fix was also applied to the Swift6 template and that Swift6 samples should be re-generated, but the diff only changes swift5/api.mustache. swift6/api.mustache:307 still passes the unannotated dictionary literal directly to mapValuesToQueryItems, so Swift6-generated code for operations with many query parameters keeps hitting the same 'expression too complex' compile-time blow-up this PR fixes. Either apply the same annotation to the Swift6 template (using the `(wrappedValue: (any Sendable)?, isExplode: Bool)` tuple type matching its APIHelper signature) or correct the PR description and rollout notes so they don't claim Swift6 was changed.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

{{/bodyParam}}{{#hasQueryParams}}
var localVariableUrlComponents = URLComponents(string: localVariableURLString)
localVariableUrlComponents?.queryItems = APIHelper.mapValuesToQueryItems([{{^queryParams}}:{{/queryParams}}
let localVariableQueryParameters: [String: (wrappedValue: Any?, isExplode: Bool)] = [{{^queryParams}}:{{/queryParams}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When an operation has a query parameter named localVariableQueryParameters, this declaration collides with the generated method parameter and makes the Swift client fail to compile. Generate a collision-free local name or scope the typed dictionary separately.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/swift5/api.mustache, line 432:

<comment>When an operation has a query parameter named `localVariableQueryParameters`, this declaration collides with the generated method parameter and makes the Swift client fail to compile. Generate a collision-free local name or scope the typed dictionary separately.</comment>

<file context>
@@ -429,11 +429,12 @@ extension {{projectName}}API {
 {{/bodyParam}}{{#hasQueryParams}}
         var localVariableUrlComponents = URLComponents(string: localVariableURLString)
-        localVariableUrlComponents?.queryItems = APIHelper.mapValuesToQueryItems([{{^queryParams}}:{{/queryParams}}
+        let localVariableQueryParameters: [String: (wrappedValue: Any?, isExplode: Bool)] = [{{^queryParams}}:{{/queryParams}}
             {{#queryParams}}
             {{> _param}},
</file context>

{{/queryParams}}
]){{/hasQueryParams}}{{^hasQueryParams}}
]
localVariableUrlComponents?.queryItems = APIHelper.mapValuesToQueryItems(localVariableQueryParameters){{/hasQueryParams}}{{^hasQueryParams}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The PR description states the typed-dictionary fix was also applied to the Swift6 template and that Swift6 samples should be re-generated, but the diff only changes swift5/api.mustache. swift6/api.mustache:307 still passes the unannotated dictionary literal directly to mapValuesToQueryItems, so Swift6-generated code for operations with many query parameters keeps hitting the same 'expression too complex' compile-time blow-up this PR fixes. Either apply the same annotation to the Swift6 template (using the (wrappedValue: (any Sendable)?, isExplode: Bool) tuple type matching its APIHelper signature) or correct the PR description and rollout notes so they don't claim Swift6 was changed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/swift5/api.mustache, line 437:

<comment>The PR description states the typed-dictionary fix was also applied to the Swift6 template and that Swift6 samples should be re-generated, but the diff only changes swift5/api.mustache. swift6/api.mustache:307 still passes the unannotated dictionary literal directly to mapValuesToQueryItems, so Swift6-generated code for operations with many query parameters keeps hitting the same 'expression too complex' compile-time blow-up this PR fixes. Either apply the same annotation to the Swift6 template (using the `(wrappedValue: (any Sendable)?, isExplode: Bool)` tuple type matching its APIHelper signature) or correct the PR description and rollout notes so they don't claim Swift6 was changed.</comment>

<file context>
@@ -429,11 +429,12 @@ extension {{projectName}}API {
             {{/queryParams}}
-        ]){{/hasQueryParams}}{{^hasQueryParams}}
+        ]
+        localVariableUrlComponents?.queryItems = APIHelper.mapValuesToQueryItems(localVariableQueryParameters){{/hasQueryParams}}{{^hasQueryParams}}
         let localVariableUrlComponents = URLComponents(string: localVariableURLString){{/hasQueryParams}}
 
</file context>

@wiebren

wiebren commented Sep 4, 2026

Copy link
Copy Markdown
Author

The ci/bitrise check fails in the 'Run Swift6 tests' step on swift6/combineLibrary - a sample this PR does not touch. The same failure occurs on every Bitrise build of this app since February 2026, including master pushes (e.g. build a5ad0d4b, 2026-08-26), so it is a pre-existing CI issue on the osx-xcode-26.2.x stack, unrelated to this change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant