Skip to content

Conversation

@morticue
Copy link

@morticue morticue commented Jan 14, 2026

  • Added automated tests
  • Documented for all relevant versions
  • Updated the changelog

Changes

Fix codegen skipping type classes in inline fragments with shared field names

The codegen was only generating the first type class when multiple inline
fragments selected the same field name but returned different concrete types.

For example, given this query:

... on NotePublishedCompanyUpdateChange {
    article { pushNotificationBody }
}
... on ResearchUpdatePublishedCompanyUpdateChange {
    article { pushNotificationBody }
}
... on InitiationCoveragePublishedCompanyUpdateChange {
    article { pushNotificationBody }
}

Where each article field resolves to a different concrete type (Note, Update,
InitiationCoverage), only the Note.php class would get generated. The other two
were skipped, even though the generated code still referenced them in the type
converters. This obviously broke static analysis and caused runtime errors.

The problem was in OperationStack::setSelection(). It used ??= which meant
"only set if not already set":

$this->selections[$namespace] ??= $selection;

So when processing the fragments:

  • First one sets selections for Note - works fine
  • Second one tries to add Update, but selections[$namespace] already exists,
    so it gets ignored
  • Same thing happens with InitiationCoverage

Changed it to merge selections by adding only new types that haven't been
processed yet:

if (!isset($this->selections[$namespace])) {
    $this->selections[$namespace] = $selection;
} else {
    // Only add new types, don't overwrite existing ones
    // (we already processed that type's subtree)
    foreach ($selection as $typeName => $builder) {
        if (!isset($this->selections[$namespace][$typeName])) {
            $this->selections[$namespace][$typeName] = $builder;
        }
    }
}

This selective merging ensures all types get generated while avoiding
reprocessing of types that were already handled (which would lose their
field selections).

This only affects schemas where you have inline fragments on interfaces with
fields that return different concrete types per implementation. Union types
already worked because they handle all possible types in a single pass.->

Breaking changes

When multiple inline fragments selected the same field name with different
concrete return types, only the first type class was generated. This caused
missing class errors for subsequent fragments.

The bug was in OperationStack::setSelection() using `??=` which ignored
subsequent selections for the same namespace. Changed to conditionally merge
new types while preserving existing ones to avoid reprocessing subtrees.
Added examples/inline-fragments/ to demonstrate the bug fix.

The example has two inline fragments selecting a 'content' field with
different return types (ArticleContent vs VideoContent). Without the fix,
only the first type class gets generated.

- Add examples/inline-fragments/ with schema and query
- Update tests/Examples.php and Makefile
- Remove tests/Unit/Codegen/OperationStackTest.php
@spawnia
Copy link
Owner

spawnia commented Jan 20, 2026

Could you please merge the latest master into your branch? I tried to do it but don't have push access to your fork.

@morticue
Copy link
Author

Merged in the master branch now 👍

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