Skip to content

Conversation

@mathpirate
Copy link
Contributor

@mathpirate mathpirate commented Oct 7, 2025

Fix wrapper type handling for aliases and recursive types

This PR addresses two related issues in wrapper type schema generation that were causing incorrect schema output for CommonTools wrapper types (Cell, Stream, OpaqueRef, Default).

Issue 1: Dangling stack references for anonymous recursive wrapper types

Problem: When processing wrapper types containing anonymous recursive types (like Cell<RecursiveItem[]> where RecursiveItem is recursive), the stack key was being deleted prematurely from definitionStack before child type processing completed. This caused the recursive detection to fail, leading to infinite loops or missing $ref entries.

Example:
type RecursiveItem = { name: string; children?: RecursiveItem[] };
type Config = { items: Cell<RecursiveItem[]> };

Fix: Move stack deletion to occur after child type formatting completes, ensuring the stack correctly tracks active processing and detects recursion.

Issue 2: Generic type parameters extracted instead of concrete types

Problem: When processing wrapper aliases that contain generic type parameters, the code was extracting types from alias declarations (which have generics like T) instead of from usage sites (which have concrete types like string or number).

Example:
type NumberList = T[];
type MyCell = Cell<NumberList>;

// Before: { asCell: true, type: "array", items: {} } ❌ empty items
// After: { asCell: true, type: "array", items: { "type": "number" } } ✓

Fixes:

  • Filter out type parameter nodes by checking TypeFlags.TypeParameter in wrapper and array formatters
  • Fix formatChildType context inheritance bug where child types incorrectly inherited parent typeNode, causing type/node mismatches
  • Improve Default wrapper node selection to prefer usage-site nodes with concrete type arguments
  • Update test fixtures to use realistic interface definitions matching actual CommonTools wrappers

Impact

Both issues affected schema generation for wrapper types, causing:

  • Missing or incorrect $ref entries for recursive wrapper structures
  • Empty schemas ({}) where concrete type information should appear
  • Type checking failures in generated schemas

Summary by cubic

Fixes schema generation for CommonTools wrappers and aliases so recursive structures emit correct $refs and wrapper inner types are concrete, not empty.

  • Bug Fixes

    • Prevent dangling $refs in recursive wrappers by keeping the parent on the stack and emitting refs correctly.
    • Detect wrappers via typeNode and alias chains (Default, Cell, Stream, OpaqueRef) to handle aliases reliably.
    • Ignore generic type parameters when extracting inner types and array elements; use usage-site concrete types.
    • Fix child formatting context so it doesn’t inherit the parent typeNode, avoiding type/node mismatches.
    • Prefer real usage-site nodes and avoid synthetic nodes when passing typeNodes to formatters.
  • Refactors

    • Added detectWrapperViaNode and resolveWrapperNode helpers for consistent wrapper detection and alias resolution.
    • Streamlined CommonTools formatter to use resolved nodes and type info for inner type extraction.

Reveals bug where anonymous recursive types inside wrappers
(e.g., Default<{ self: Root }, {}>) create dangling $ref because
the synthetic name is created by cycle detection but never stored
in definitions due to isWrapperContext check.

The test shows that when an anonymous object type containing a
recursive reference is wrapped in Default/Cell/Stream, the cycle
detection correctly creates AnonymousType_1 and emits a $ref to it,
but the definition is never stored in $defs because line 224 in
schema-generator.ts prevents synthetic names from being used when
isWrapperContext is true.

Reported by automated PR review bot.
…ng references.

  Root Cause: When a named type (like SchemaRoot) was already in progress or existed in definitions, the code was incorrectly deleting it from the
  definitionStack at line 188-190. This broke cycle detection for any child types being formatted, because the parent type was no longer on the stack
  to detect the cycle.

  The Fix: Removed the incorrect definitionStack.delete() call at lines 188-190. This deletion was logically wrong because:
  1. We never pushed to the stack at that point (we return early before line 210's push)
  2. If something with that key WAS on the stack, it was from a parent call, and deleting it broke cycle detection

  Test Updates:
  - Updated anonymous-recursive-wrapper.expected.json to reflect the correct behavior: anonymous objects that appear only once should be inlined, not
  hoisted to separate definitions
  - The test now passes with the schema correctly inlining the anonymous object and using  for SchemaRoot's cycle
…eters

  Fixes improper type extraction from wrapper alias declarations that contain
  generic type parameters instead of concrete types.

  When processing wrapper aliases like 'type MyCell<T> = Cell<T>' or nested
  aliases like 'Cell<NumberList<number>>' where 'NumberList<T> = T[]', the
  code was incorrectly extracting type information from alias declarations
  (which contain generic 'T') instead of from usage sites (which have
  concrete types like 'string' or 'number').

  Key changes:
  - Filter out type parameter nodes in wrapper and array formatters by checking
    if extracted types have TypeFlags.TypeParameter flag
  - Fix formatChildType to properly clear parent typeNode context using
    destructuring, preventing mismatched type/node pairs in recursive calls
  - Improve Default wrapper node selection to prefer usage-site nodes with
    concrete type arguments over alias declaration nodes
  - Update wrapper-aliases test fixture to use realistic interface definitions
    matching actual CommonTools wrappers instead of simple type erasure

  Before: 'Cell<NumberList<number>>' generated 'items: {}' (empty object)
  After: 'Cell<NumberList<number>>' generates 'items: { type: number }'

  Related to commit 16aceaf which fixed anonymous recursive wrapper refs.
Copy link
Contributor

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

Choose a reason for hiding this comment

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

1 issue found across 8 files

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="packages/schema-generator/test/fixtures/schema/anonymous-recursive-wrapper.expected.json">

<violation number="1" location="packages/schema-generator/test/fixtures/schema/anonymous-recursive-wrapper.expected.json:16">
The default value for `field` is `{}`, but this object simultaneously requires a `self` property. That default violates the schema and will yield invalid data for any consumer that applies defaults; please remove or adjust the default so it satisfies the required properties.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

"required": [
"self"
],
"default": {}
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 7, 2025

Choose a reason for hiding this comment

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

The default value for field is {}, but this object simultaneously requires a self property. That default violates the schema and will yield invalid data for any consumer that applies defaults; please remove or adjust the default so it satisfies the required properties.

Prompt for AI agents
Address the following comment on packages/schema-generator/test/fixtures/schema/anonymous-recursive-wrapper.expected.json at line 16:

<comment>The default value for `field` is `{}`, but this object simultaneously requires a `self` property. That default violates the schema and will yield invalid data for any consumer that applies defaults; please remove or adjust the default so it satisfies the required properties.</comment>

<file context>
@@ -0,0 +1,25 @@
+          &quot;required&quot;: [
+            &quot;self&quot;
+          ],
+          &quot;default&quot;: {}
+        }
+      },
</file context>

✅ Addressed in 69377c0

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks cubic, fixed this

@mathpirate mathpirate merged commit 0e3fd19 into main Oct 7, 2025
8 checks passed
@mathpirate mathpirate deleted the fix/wrapper-alias-handling branch October 7, 2025 20:30
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.

2 participants