protected function findConflict(
        string $responseName,
        FieldContext $fieldA,
        FieldContext $fieldB,
        bool $parentFieldsAreMutuallyExclusive
    ): ?Conflict {
        $parentTypeA = $fieldA->getParentType();
        $parentTypeB = $fieldB->getParentType();

        // If it is known that two fields could not possibly apply at the same
        // time, due to the parent types, then it is safe to permit them to diverge
        // in aliased field or arguments used as they will not present any ambiguity
        // by differing.
        // It is known that two parent types could never overlap if they are
        // different Object types. Interface or Union types might overlap - if not
        // in the current state of the schema, then perhaps in some future version,
        // thus may not safely diverge.
        $areMutuallyExclusive = $parentFieldsAreMutuallyExclusive
            || ($parentTypeA !== $parentTypeB
                && $parentTypeA instanceof ObjectType
                && $parentTypeB instanceof ObjectType);

        $nodeA = $fieldA->getNode();
        $nodeB = $fieldB->getNode();

        $definitionA = $fieldA->getDefinition();
        $definitionB = $fieldB->getDefinition();

        if (!$areMutuallyExclusive) {
            // Two aliases must refer to the same field.
            $nameA = $nodeA->getNameValue();
            $nameB = $nodeB->getNameValue();

            if ($nameA !== $nameB) {
                return new Conflict(
                    $responseName,
                    sprintf('%s and %s are different fields', $nameA, $nameB),
                    [$nodeA],
                    [$nodeB]
                );
            }

            // Two field calls must have the same arguments.
            if (!ValueHelper::compareArguments($nodeA->getArguments(), $nodeB->getArguments())) {
                return new Conflict(
                    $responseName,
                    'they have differing arguments',
                    [$nodeA],
                    [$nodeB]
                );
            }
        }

        // The return type for each field.
        $typeA = null !== $definitionA ? $definitionA->getType() : null;
        $typeB = null !== $definitionB ? $definitionB->getType() : null;

        if (null !== $typeA && null !== $typeB && TypeHelper::compareTypes($typeA, $typeB)) {
            return new Conflict(
                $responseName,
                sprintf('they return conflicting types %s and %s', (string)$typeA, (string)$typeB),
                [$nodeA],
                [$nodeB]
            );
        }

        // Collect and compare sub-fields. Use the same "visited fragment names" list
        // for both collections so fields in a fragment reference are never
        // compared to themselves.
        $selectionSetA = $nodeA->getSelectionSet();
        $selectionSetB = $nodeB->getSelectionSet();

        if (null !== $selectionSetA && null !== $selectionSetB) {
            $conflicts = $this->findConflictsBetweenSubSelectionSets(
                getNamedType($typeA),
                $selectionSetA,
                getNamedType($typeB),
                $selectionSetB,
                $areMutuallyExclusive
            );

            return $this->subfieldConflicts($conflicts, $responseName, $nodeA, $nodeB);
        }

        return null;
    }