Skip to content

Stop boxing types that recurse only through an array or dictionary - #910

Merged
simonjbeaumont merged 2 commits into
apple:mainfrom
eyupcanakman:fix/recursive-boxing-containers
Jul 3, 2026
Merged

Stop boxing types that recurse only through an array or dictionary#910
simonjbeaumont merged 2 commits into
apple:mainfrom
eyupcanakman:fix/recursive-boxing-containers

Conversation

@eyupcanakman

Copy link
Copy Markdown
Contributor

Motivation

The generator boxes a type whose only self-reference is reached through an array or dictionary. Swift arrays and dictionaries keep their elements in heap-allocated storage, so a reference reached only through them cannot form a value-type cycle and the type does not need a CopyOnWriteBox. Supporting-recursive-types.md already notes that arrays and dictionaries can be considered boxed, so the containing type does not require boxing. Fixes #682.

Modifications

  • In ExistingTypeDescription.referencedSchemaComponentName, return nil for .array and .dictionaryValue instead of recursing into them. .optional and .any still recurse.

Result

A type that recurses only through an array or dictionary is generated as a plain struct with stored properties, not a boxed type.

Test Plan

Added two snippet reference tests covering recursion through an array and through a dictionary.

Arrays and dictionaries already break the reference cycle, so a type that reaches itself only through one of them does not need CopyOnWriteBox storage. The recursion detector now skips array and dictionary references when collecting cycle-forming edges, which matches the behavior described in Supporting-recursive-types.md. Direct and optional self-references are still boxed.

Closes apple#682

@simonjbeaumont simonjbeaumont left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This looks good and is a really nice catch. Thanks!

Could we extend the tests to include the mixed case where the schema recurses through a direct ref AND a container.

  Node:
    type: object
    properties:
      parent: { $ref: '#/components/schemas/Node' }        # direct -> must still box
      children:
        type: array
        items: { $ref: '#/components/schemas/Node' }        # container -> must not prevent the box

This presumably should still box.

@eyupcanakman

Copy link
Copy Markdown
Contributor Author

Added two mixed-case tests, a Node with a direct parent ref alongside a children array, and the same with a children dictionary. Both still box, since the direct ref forms the value cycle and only the container path stopped contributing one.

@simonjbeaumont simonjbeaumont left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the contribution!

@simonjbeaumont simonjbeaumont added the 🔨 semver/patch No public API change. label Jul 3, 2026
@simonjbeaumont
simonjbeaumont merged commit af9a2a1 into apple:main Jul 3, 2026
49 of 50 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 semver/patch No public API change.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The generator is boxing properties of recursive types that do not need boxing

2 participants