@@ -1406,7 +1406,10 @@ export class NamedFragments {
1406
1406
return undefined ;
1407
1407
}
1408
1408
1409
- const rebasedSelection = fragment . selectionSet . rebaseOn ( { parentType : rebasedType , fragments : newFragments , errorIfCannotRebase : false } ) ;
1409
+ let rebasedSelection = fragment . selectionSet . rebaseOn ( { parentType : rebasedType , fragments : newFragments , errorIfCannotRebase : false } ) ;
1410
+ // Rebasing can leave some inefficiencies in some case (particularly when a spread has to be "expanded", see `FragmentSpreadSelection.rebaseOn`),
1411
+ // so we do a top-level normalization to keep things clean.
1412
+ rebasedSelection = rebasedSelection . normalize ( { parentType : rebasedType } ) ;
1410
1413
return this . selectionSetIsWorthUsing ( rebasedSelection )
1411
1414
? new NamedFragmentDefinition ( schema , fragment . name , rebasedType ) . setSelectionSet ( rebasedSelection )
1412
1415
: undefined ;
@@ -1421,9 +1424,11 @@ export class NamedFragments {
1421
1424
// dependency order, we know that `newFragments` will have every fragments that should be
1422
1425
// kept/not expanded.
1423
1426
const updatedSelectionSet = fragment . selectionSet . expandFragments ( newFragments ) ;
1427
+ // Note that if we did expanded some fragments (the updated selection is not the original one), then the
1428
+ // results may not be fully normalized, so we do it to be sure.
1424
1429
return updatedSelectionSet === fragment . selectionSet
1425
1430
? fragment
1426
- : fragment . withUpdatedSelectionSet ( updatedSelectionSet ) ;
1431
+ : fragment . withUpdatedSelectionSet ( updatedSelectionSet . normalize ( { parentType : updatedSelectionSet . parentType } ) ) ;
1427
1432
} else {
1428
1433
return undefined ;
1429
1434
}
@@ -3558,7 +3563,8 @@ class FragmentSpreadSelection extends FragmentSelection {
3558
3563
// If we're rebasing on a _different_ schema, then we *must* have fragments, since reusing
3559
3564
// `this.fragments` would be incorrect. If we're on the same schema though, we're happy to default
3560
3565
// to `this.fragments`.
3561
- assert ( fragments || this . parentType . schema ( ) === parentType . schema ( ) , `Must provide fragments is rebasing on other schema` ) ;
3566
+ const rebaseOnSameSchema = this . parentType . schema ( ) === parentType . schema ( ) ;
3567
+ assert ( fragments || rebaseOnSameSchema , `Must provide fragments is rebasing on other schema` ) ;
3562
3568
const newFragments = fragments ?? this . fragments ;
3563
3569
const namedFragment = newFragments . get ( this . namedFragment . name ) ;
3564
3570
// If we're rebasing on another schema (think a subgraph), then named fragments will have been rebased on that, and some
@@ -3569,6 +3575,31 @@ class FragmentSpreadSelection extends FragmentSelection {
3569
3575
validate ( ! errorIfCannotRebase , ( ) => `Cannot rebase ${ this . toString ( false ) } if it isn't part of the provided fragments` ) ;
3570
3576
return undefined ;
3571
3577
}
3578
+
3579
+ // Lastly, if we rebase on a different schema, it's possible the fragment type does not intersect the
3580
+ // parent type. For instance, the parent type could be some object type T while the fragment is an
3581
+ // interface I, and T may implement I in the supergraph, but not in a particular subgraph (of course,
3582
+ // if I don't exist at all in the subgraph, then we'll have exited above, but I may exist in the
3583
+ // subgraph, just not be implemented by T for some reason). In that case, we can't reuse the fragment
3584
+ // as its spread is essentially invalid in that position, so we have to replace it by the expansion
3585
+ // of that fragment, which we rebase on the parentType (which in turn, will remove anythings within
3586
+ // the fragment selection that needs removing, potentially everything).
3587
+ if ( ! rebaseOnSameSchema && ! runtimeTypesIntersects ( parentType , namedFragment . typeCondition ) ) {
3588
+ // Note that we've used the rebased `namedFragment` to check the type intersection because we needed to
3589
+ // compare runtime types "for the schema we're rebasing into". But now that we're deciding to not reuse
3590
+ // this rebased fragment, what we rebase is the selection set of the non-rebased fragment. And that's
3591
+ // important because the very logic we're hitting here may need to happen inside the rebase do the
3592
+ // fragment selection, but that logic would not be triggered if we used the rebased `namedFragment` since
3593
+ // `rebaseOnSameSchema` would then be 'true'.
3594
+ const expanded = this . namedFragment . selectionSet . rebaseOn ( { parentType, fragments, errorIfCannotRebase } ) ;
3595
+ // In theory, we could return the selection set directly, but making `Selection.rebaseOn` sometimes
3596
+ // return a `SelectionSet` complicate things quite a bit. So instead, we encapsulate the selection set
3597
+ // in an "empty" inline fragment. This make for non-really-optimal selection sets in the (relatively
3598
+ // rare) case where this is triggered, but in practice this "inefficiency" is removed by future calls
3599
+ // to `normalize`.
3600
+ return expanded . isEmpty ( ) ? undefined : new InlineFragmentSelection ( new FragmentElement ( parentType ) , expanded ) ;
3601
+ }
3602
+
3572
3603
return new FragmentSpreadSelection (
3573
3604
parentType ,
3574
3605
newFragments ,
0 commit comments