Skip to content

chore(deps): update phpstan packages to v2 (major) #1635

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 11, 2025
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
@@ -23,14 +23,14 @@
"nyholm/psr7": "^1.5",
"phpbench/phpbench": "^1.2",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "1.12.12",
"phpstan/phpstan-phpunit": "1.4.1",
"phpstan/phpstan-strict-rules": "1.6.1",
"phpstan/phpstan": "2.1.8",
"phpstan/phpstan-phpunit": "2.0.4",
"phpstan/phpstan-strict-rules": "2.0.3",
"phpunit/phpunit": "^9.5 || ^10.5.21 || ^11",
"psr/http-message": "^1 || ^2",
"react/http": "^1.6",
"react/promise": "^2.0 || ^3.0",
"rector/rector": "^1.0",
"rector/rector": "^2.0",
"symfony/polyfill-php81": "^1.23",
"symfony/var-exporter": "^5 || ^6 || ^7",
"thecodingmachine/safe": "^1.3 || ^2"
2 changes: 1 addition & 1 deletion generate-class-reference.php
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@
];

/**
* @param ReflectionClass<object> $class
* @param ReflectionClass<covariant object> $class
* @param array{constants?: bool, props?: bool, methods?: bool} $options
*
* @throws ExceptionInterface
60 changes: 30 additions & 30 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -1,91 +1,91 @@
parameters:
ignoreErrors:
-
message: "#^Unable to resolve the template type TCloneable in call to method static method GraphQL\\\\Language\\\\AST\\\\Node\\:\\:cloneValue\\(\\)$#"
message: '#^Unable to resolve the template type TCloneable in call to method static method GraphQL\\Language\\AST\\Node\:\:cloneValue\(\)$#'
identifier: argument.templateType
count: 1
path: src/Language/AST/Node.php

-
message: "#^Variable property access on TCloneable of TNode of GraphQL\\\\Language\\\\AST\\\\Node\\.$#"
message: '#^Variable property access on TCloneable of TNode of GraphQL\\Language\\AST\\Node\.$#'
identifier: property.dynamicName
count: 1
path: src/Language/AST/Node.php

-
message: "#^Variable property access on GraphQL\\\\Language\\\\AST\\\\Node\\.$#"
message: '#^Variable property access on GraphQL\\Language\\AST\\Node\.$#'
identifier: property.dynamicName
count: 1
path: src/Language/Visitor.php

-
message: "#^Variable property access on GraphQL\\\\Language\\\\AST\\\\Node\\|null\\.$#"
message: '#^Variable property access on GraphQL\\Language\\AST\\Node\|null\.$#'
identifier: property.dynamicName
count: 1
path: src/Language/Visitor.php

-
message: "#^Parameter \\#1 \\$config of class GraphQL\\\\Type\\\\Definition\\\\Argument constructor expects array\\{name\\: string, type\\: \\(callable\\(\\)\\: \\(GraphQL\\\\Type\\\\Definition\\\\InputType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\)\\|\\(GraphQL\\\\Type\\\\Definition\\\\InputType&GraphQL\\\\Type\\\\Definition\\\\Type\\), defaultValue\\?\\: mixed, description\\?\\: string\\|null, deprecationReason\\?\\: string\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\InputValueDefinitionNode\\|null\\}, non\\-empty\\-array given\\.$#"
count: 1
path: src/Type/Definition/Argument.php

-
message: "#^Property GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\:\\:\\$interfaces \\(array\\<int, GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\>\\) does not accept array\\<int, GraphQL\\\\Type\\\\Definition\\\\Type\\>\\.$#"
message: '#^Property GraphQL\\Type\\Definition\\InterfaceType\:\:\$interfaces \(array\<int, GraphQL\\Type\\Definition\\InterfaceType\>\) does not accept array\<int, GraphQL\\Type\\Definition\\Type\>\.$#'
identifier: assign.propertyType
count: 1
path: src/Type/Definition/InterfaceType.php

-
message: "#^Unable to resolve the template type T in call to method static method GraphQL\\\\Type\\\\Schema\\:\\:resolveType\\(\\)$#"
message: '#^Unable to resolve the template type T in call to method static method GraphQL\\Type\\Schema\:\:resolveType\(\)$#'
identifier: argument.templateType
count: 1
path: src/Type/Definition/InterfaceType.php

-
message: "#^Property GraphQL\\\\Type\\\\Definition\\\\ObjectType\\:\\:\\$interfaces \\(array\\<int, GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\>\\) does not accept array\\<int, GraphQL\\\\Type\\\\Definition\\\\Type\\>\\.$#"
message: '#^Property GraphQL\\Type\\Definition\\ObjectType\:\:\$interfaces \(array\<int, GraphQL\\Type\\Definition\\InterfaceType\>\) does not accept array\<int, GraphQL\\Type\\Definition\\Type\>\.$#'
identifier: assign.propertyType
count: 1
path: src/Type/Definition/ObjectType.php

-
message: "#^Unable to resolve the template type T in call to method static method GraphQL\\\\Type\\\\Schema\\:\\:resolveType\\(\\)$#"
message: '#^Unable to resolve the template type T in call to method static method GraphQL\\Type\\Schema\:\:resolveType\(\)$#'
identifier: argument.templateType
count: 1
path: src/Type/Definition/ObjectType.php

-
message: "#^Method GraphQL\\\\Type\\\\Definition\\\\UnionType\\:\\:getTypes\\(\\) should return array\\<int, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\> but returns array\\<int, GraphQL\\\\Type\\\\Definition\\\\Type\\>\\.$#"
message: '#^Method GraphQL\\Type\\Definition\\UnionType\:\:getTypes\(\) should return array\<int, GraphQL\\Type\\Definition\\ObjectType\> but returns array\<int, GraphQL\\Type\\Definition\\Type\>\.$#'
identifier: return.type
count: 1
path: src/Type/Definition/UnionType.php

-
message: "#^Property GraphQL\\\\Type\\\\Definition\\\\UnionType\\:\\:\\$types \\(array\\<int, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\>\\) does not accept array\\<int, GraphQL\\\\Type\\\\Definition\\\\Type\\>\\.$#"
message: '#^Property GraphQL\\Type\\Definition\\UnionType\:\:\$types \(array\<int, GraphQL\\Type\\Definition\\ObjectType\>\) does not accept array\<int, GraphQL\\Type\\Definition\\Type\>\.$#'
identifier: assign.propertyType
count: 1
path: src/Type/Definition/UnionType.php

-
message: "#^Unable to resolve the template type T in call to method static method GraphQL\\\\Type\\\\Schema\\:\\:resolveType\\(\\)$#"
message: '#^Unable to resolve the template type T in call to method static method GraphQL\\Type\\Schema\:\:resolveType\(\)$#'
identifier: argument.templateType
count: 1
path: src/Type/Definition/UnionType.php

-
message: "#^Method GraphQL\\\\Type\\\\Schema\\:\\:resolveType\\(\\) should return T of GraphQL\\\\Type\\\\Definition\\\\Type but returns \\(callable&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|T of GraphQL\\\\Type\\\\Definition\\\\Type\\.$#"
message: '#^Method GraphQL\\Type\\Schema\:\:resolveType\(\) should return T of GraphQL\\Type\\Definition\\Type but returns \(callable&GraphQL\\Type\\Definition\\Type\)\|T of GraphQL\\Type\\Definition\\Type\.$#'
identifier: return.type
count: 1
path: src/Type/Schema.php

-
message: "#^Variable property access on GraphQL\\\\Language\\\\AST\\\\ArgumentNode\\|GraphQL\\\\Language\\\\AST\\\\BooleanValueNode\\|GraphQL\\\\Language\\\\AST\\\\DirectiveDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\DirectiveNode\\|GraphQL\\\\Language\\\\AST\\\\DocumentNode\\|GraphQL\\\\Language\\\\AST\\\\EnumTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\EnumTypeExtensionNode\\|GraphQL\\\\Language\\\\AST\\\\EnumValueDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\EnumValueNode\\|GraphQL\\\\Language\\\\AST\\\\FieldDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\FieldNode\\|GraphQL\\\\Language\\\\AST\\\\FloatValueNode\\|GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\FragmentSpreadNode\\|GraphQL\\\\Language\\\\AST\\\\InlineFragmentNode\\|GraphQL\\\\Language\\\\AST\\\\InputObjectTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\InputObjectTypeExtensionNode\\|GraphQL\\\\Language\\\\AST\\\\InputValueDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\InterfaceTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\InterfaceTypeExtensionNode\\|GraphQL\\\\Language\\\\AST\\\\IntValueNode\\|GraphQL\\\\Language\\\\AST\\\\ListTypeNode\\|GraphQL\\\\Language\\\\AST\\\\ListValueNode\\|GraphQL\\\\Language\\\\AST\\\\NamedTypeNode\\|GraphQL\\\\Language\\\\AST\\\\NameNode\\|GraphQL\\\\Language\\\\AST\\\\NonNullTypeNode\\|GraphQL\\\\Language\\\\AST\\\\NullValueNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectFieldNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectTypeExtensionNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectValueNode\\|GraphQL\\\\Language\\\\AST\\\\OperationDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\OperationTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\ScalarTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\ScalarTypeExtensionNode\\|GraphQL\\\\Language\\\\AST\\\\SchemaDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\SelectionSetNode\\|GraphQL\\\\Language\\\\AST\\\\StringValueNode\\|GraphQL\\\\Language\\\\AST\\\\UnionTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\UnionTypeExtensionNode\\|GraphQL\\\\Language\\\\AST\\\\VariableDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\VariableNode\\.$#"
message: '#^Variable property access on GraphQL\\Language\\AST\\ArgumentNode\|GraphQL\\Language\\AST\\BooleanValueNode\|GraphQL\\Language\\AST\\DirectiveDefinitionNode\|GraphQL\\Language\\AST\\DirectiveNode\|GraphQL\\Language\\AST\\DocumentNode\|GraphQL\\Language\\AST\\EnumTypeDefinitionNode\|GraphQL\\Language\\AST\\EnumTypeExtensionNode\|GraphQL\\Language\\AST\\EnumValueDefinitionNode\|GraphQL\\Language\\AST\\EnumValueNode\|GraphQL\\Language\\AST\\FieldDefinitionNode\|GraphQL\\Language\\AST\\FieldNode\|GraphQL\\Language\\AST\\FloatValueNode\|GraphQL\\Language\\AST\\FragmentDefinitionNode\|GraphQL\\Language\\AST\\FragmentSpreadNode\|GraphQL\\Language\\AST\\InlineFragmentNode\|GraphQL\\Language\\AST\\InputObjectTypeDefinitionNode\|GraphQL\\Language\\AST\\InputObjectTypeExtensionNode\|GraphQL\\Language\\AST\\InputValueDefinitionNode\|GraphQL\\Language\\AST\\InterfaceTypeDefinitionNode\|GraphQL\\Language\\AST\\InterfaceTypeExtensionNode\|GraphQL\\Language\\AST\\IntValueNode\|GraphQL\\Language\\AST\\ListTypeNode\|GraphQL\\Language\\AST\\ListValueNode\|GraphQL\\Language\\AST\\NamedTypeNode\|GraphQL\\Language\\AST\\NameNode\|GraphQL\\Language\\AST\\NonNullTypeNode\|GraphQL\\Language\\AST\\NullValueNode\|GraphQL\\Language\\AST\\ObjectFieldNode\|GraphQL\\Language\\AST\\ObjectTypeDefinitionNode\|GraphQL\\Language\\AST\\ObjectTypeExtensionNode\|GraphQL\\Language\\AST\\ObjectValueNode\|GraphQL\\Language\\AST\\OperationDefinitionNode\|GraphQL\\Language\\AST\\OperationTypeDefinitionNode\|GraphQL\\Language\\AST\\ScalarTypeDefinitionNode\|GraphQL\\Language\\AST\\ScalarTypeExtensionNode\|GraphQL\\Language\\AST\\SchemaDefinitionNode\|GraphQL\\Language\\AST\\SelectionSetNode\|GraphQL\\Language\\AST\\StringValueNode\|GraphQL\\Language\\AST\\UnionTypeDefinitionNode\|GraphQL\\Language\\AST\\UnionTypeExtensionNode\|GraphQL\\Language\\AST\\VariableDefinitionNode\|GraphQL\\Language\\AST\\VariableNode\.$#'
identifier: property.dynamicName
count: 1
path: src/Utils/AST.php

-
message: "#^Variable property access on mixed\\.$#"
message: '#^Variable property access on mixed\.$#'
identifier: property.dynamicName
count: 1
path: src/Utils/AST.php

-
message: "#^Method GraphQL\\\\Validator\\\\Rules\\\\KnownDirectives\\:\\:getDirectiveLocationForASTPath\\(\\) has parameter \\$ancestors with generic class GraphQL\\\\Language\\\\AST\\\\NodeList but does not specify its types\\: T$#"
message: '#^Method GraphQL\\Validator\\Rules\\KnownDirectives\:\:getDirectiveLocationForASTPath\(\) has parameter \$ancestors with generic class GraphQL\\Language\\AST\\NodeList but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Validator/Rules/KnownDirectives.php

-
message: "#^Method GraphQL\\\\Validator\\\\Rules\\\\OverlappingFieldsCanBeMerged\\:\\:getFieldsAndFragmentNames\\(\\) should return array\\{array\\<string, array\\<int, array\\{GraphQL\\\\Type\\\\Definition\\\\Type, GraphQL\\\\Language\\\\AST\\\\FieldNode, GraphQL\\\\Type\\\\Definition\\\\FieldDefinition\\|null\\}\\>\\>, array\\<int, string\\>\\} but returns array\\{mixed, array\\<int, int\\|string\\>\\}\\.$#"
count: 1
path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php

-
message: "#^SplObjectStorage\\<GraphQL\\\\Language\\\\AST\\\\SelectionSetNode, array\\{array\\<string, array\\<int, array\\{GraphQL\\\\Type\\\\Definition\\\\Type, GraphQL\\\\Language\\\\AST\\\\FieldNode, GraphQL\\\\Type\\\\Definition\\\\FieldDefinition\\|null\\}\\>\\>, array\\<int, string\\>\\}\\> does not accept array\\<int, mixed\\>\\.$#"
count: 1
path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php
6 changes: 6 additions & 0 deletions phpstan/include-by-php-version.php
Original file line number Diff line number Diff line change
@@ -6,6 +6,12 @@
if (version_compare($phpversion, '8.2', '>=')) {
$includes[] = __DIR__ . '/php-at-least-8.2.neon';
}
if (version_compare($phpversion, '8.4', '<')) {
$includes[] = __DIR__ . '/php-below-8.4.neon';
}
if (version_compare($phpversion, '8.2', '<')) {
$includes[] = __DIR__ . '/php-below-8.2.neon';
}
if (version_compare($phpversion, '8.1', '<')) {
$includes[] = __DIR__ . '/php-below-8.1.neon';
}
11 changes: 11 additions & 0 deletions phpstan/php-below-8.2.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
parameters:
ignoreErrors:
-
identifier: function.alreadyNarrowedType
count: 2
path: ../src/Type/Definition/QueryPlan.php

-
identifier: function.alreadyNarrowedType
count: 2
path: ../src/Utils/TypeInfo.php
7 changes: 7 additions & 0 deletions phpstan/php-below-8.4.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
parameters:
ignoreErrors:
-
message: '#^Call\-site variance of covariant object in generic type ReflectionClass\<covariant object\> in PHPDoc tag @param for parameter \$class is redundant, template type T of object of class ReflectionClass has the same variance\.$#'
identifier: generics.callSiteVarianceRedundant
count: 1
path: ../generate-class-reference.php
1 change: 1 addition & 0 deletions src/Error/Error.php
Original file line number Diff line number Diff line change
@@ -93,6 +93,7 @@ public function __construct(

// Compute list of blame nodes.
if ($nodes instanceof \Traversable) {
/** @phpstan-ignore arrayFilter.strict */
$this->nodes = array_filter(\iterator_to_array($nodes));
} elseif (\is_array($nodes)) {
$this->nodes = array_filter($nodes);
4 changes: 2 additions & 2 deletions src/Executor/ExecutionContext.php
Original file line number Diff line number Diff line change
@@ -49,7 +49,7 @@ class ExecutionContext
*/
public $argsMapper;

/** @var array<int, Error> */
/** @var list<Error> */
public array $errors;

public PromiseAdapter $promiseAdapter;
@@ -59,7 +59,7 @@ class ExecutionContext
* @param mixed $rootValue
* @param mixed $contextValue
* @param array<string, mixed> $variableValues
* @param array<int, Error> $errors
* @param list<Error> $errors
*
* @phpstan-param FieldResolver $fieldResolver
*/
1 change: 1 addition & 0 deletions src/Executor/ExecutionResult.php
Original file line number Diff line number Diff line change
@@ -161,6 +161,7 @@ public function toArray(int $debug = DebugFlag::NONE): array
$errorsHandler = $this->errorsHandler
?? static fn (array $errors, callable $formatter): array => \array_map($formatter, $errors);

/** @phpstan-var SerializableErrors */
$handledErrors = $errorsHandler(
$this->errors,
FormattedError::prepareFormatter($this->errorFormatter, $debug)
2 changes: 1 addition & 1 deletion src/Executor/Promise/Adapter/AmpPromiseAdapter.php
Original file line number Diff line number Diff line change
@@ -126,7 +126,7 @@ public function all(iterable $promisesOrValues): Promise

/**
* @template TArgument
* @template TResult
* @template TResult of AmpPromise<mixed>
*
* @param Deferred<TResult> $deferred
* @param callable(TArgument): TResult $callback
4 changes: 2 additions & 2 deletions src/Executor/ReferenceExecutor.php
Original file line number Diff line number Diff line change
@@ -150,7 +150,7 @@ public function doExecute(): Promise
*
* @throws \Exception
*
* @return ExecutionContext|array<int, Error>
* @return ExecutionContext|list<Error>
*/
protected static function buildExecutionContext(
Schema $schema,
@@ -163,7 +163,7 @@ protected static function buildExecutionContext(
callable $argsMapper,
PromiseAdapter $promiseAdapter
) {
/** @var array<int, Error> $errors */
/** @var list<Error> $errors */
$errors = [];

/** @var array<string, FragmentDefinitionNode> $fragments */
5 changes: 5 additions & 0 deletions src/Language/AST/Node.php
Original file line number Diff line number Diff line change
@@ -79,6 +79,11 @@ protected static function cloneValue($value)
}

if ($value instanceof NodeList) {
/**
* @phpstan-var TCloneable
*
* @phpstan-ignore varTag.nativeType (PHPStan is strict about template types and sees NodeList<TNode> as potentially different from TCloneable)
*/
return $value->cloneDeep();
}

5 changes: 3 additions & 2 deletions src/Language/AST/NodeList.php
Original file line number Diff line number Diff line change
@@ -145,8 +145,9 @@ public function reindex(): void
*/
public function cloneDeep(): self
{
/** @var static<T> $cloned */
$cloned = new static([]);
/** @var array<T> $empty */
$empty = [];
$cloned = new static($empty);
foreach ($this->getIterator() as $key => $node) {
$cloned[$key] = $node->cloneDeep();
}
2 changes: 1 addition & 1 deletion src/Language/Printer.php
Original file line number Diff line number Diff line change
@@ -531,6 +531,6 @@ protected function indent(string $string): string
/** @param array<string|null> $parts */
protected function join(array $parts, string $separator = ''): string
{
return \implode($separator, \array_filter($parts));
return \implode($separator, \array_filter($parts, static fn (?string $part) => $part !== '' && $part !== null));
}
}
2 changes: 1 addition & 1 deletion src/Language/Visitor.php
Original file line number Diff line number Diff line change
@@ -478,7 +478,7 @@ public static function visitWithTypeInfo(TypeInfo $typeInfo, array $visitor): ar
/**
* @phpstan-param VisitorArray $visitor
*
* @return callable(Node $node, string $key, Node|NodeList $parent, array<int, int|string $path, array<int, Node|NodeList> $ancestors): VisitorOperation|Node|null
* @return (callable(Node $node, string $key, Node|NodeList<Node>|null $parent, array<int, int|string> $path, array<int, Node|NodeList<Node>> $ancestors): (VisitorOperation|Node|null))|(callable(Node): (VisitorOperation|void|false|null))|null
*/
protected static function extractVisitFn(array $visitor, string $kind, bool $isLeaving): ?callable
{
5 changes: 4 additions & 1 deletion src/Type/Definition/Argument.php
Original file line number Diff line number Diff line change
@@ -73,7 +73,10 @@ public static function listFromConfig(iterable $config): array
$argConfig = ['type' => $argConfig];
}

$list[] = new self($argConfig + ['name' => $name]);
/** @phpstan-var ArgumentConfig $argConfigWithName */
$argConfigWithName = $argConfig + ['name' => $name];

$list[] = new self($argConfigWithName);
}

return $list;
11 changes: 7 additions & 4 deletions src/Type/Definition/QueryPlan.php
Original file line number Diff line number Diff line change
@@ -142,7 +142,7 @@ private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes):
if ($this->groupImplementorFields) {
$this->queryPlan = ['fields' => $queryPlan];

if ($implementors) {
if ($implementors !== []) {
$this->queryPlan['implementors'] = $implementors;
}
} else {
@@ -188,7 +188,7 @@ private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $paren
'fields' => $subfields,
'args' => Values::getArgumentValues($type, $selection, $this->variableValues),
];
if ($this->groupImplementorFields && $subImplementors) {
if ($this->groupImplementorFields && $subImplementors !== []) {
$fields[$fieldName]['implementors'] = $subImplementors;
}
} elseif ($selection instanceof FragmentSpreadNode) {
@@ -256,10 +256,13 @@ private function analyzeSubFields(Type $type, SelectionSetNode $selectionSet, ar
private function mergeFields(Type $parentType, Type $type, array $fields, array $subfields, array &$implementors): array
{
if ($this->groupImplementorFields && $parentType instanceof AbstractType && ! $type instanceof AbstractType) {
$implementors[$type->name] = [
$name = $type->name;
assert(\is_string($name));

$implementors[$name] = [
'type' => $type,
'fields' => $this->arrayMergeDeep(
$implementors[$type->name]['fields'] ?? [],
$implementors[$name]['fields'] ?? [],
\array_diff_key($subfields, $fields)
),
];
2 changes: 1 addition & 1 deletion src/Utils/AST.php
Original file line number Diff line number Diff line change
@@ -250,7 +250,7 @@ public static function astFromValue($value, InputType $type): ?ValueNode
}

if (\is_float($serialized)) {
// int cast with == used for performance reasons
/** @phpstan-ignore equal.notAllowed (int cast with == used for performance reasons) */
if ((int) $serialized == $serialized) {
return new IntValueNode(['value' => (string) $serialized]);
}
2 changes: 1 addition & 1 deletion src/Utils/ASTDefinitionBuilder.php
Original file line number Diff line number Diff line change
@@ -174,7 +174,7 @@ private function makeInputValues(NodeList $values): array
*/
private function makeInputFields(array $nodes): array
{
/** @var array<int, InputValueDefinitionNode>> $fields */
/** @var array<int, InputValueDefinitionNode> $fields */
$fields = [];
foreach ($nodes as $node) {
\array_push($fields, ...$node->fields);
1 change: 0 additions & 1 deletion src/Utils/BuildClientSchema.php
Original file line number Diff line number Diff line change
@@ -499,7 +499,6 @@ private function buildInputValueDefMap(array $inputValueIntrospections): array
$map[$value['name']] = $this->buildInputValue($value);
}

// @phpstan-ignore-next-line unless the returned name was numeric, this works
return $map;
}

1 change: 1 addition & 0 deletions src/Utils/SchemaPrinter.php
Original file line number Diff line number Diff line change
@@ -157,6 +157,7 @@ protected static function printFilteredSchema(Schema $schema, callable $directiv
$elements[] = static::printType($type, $options);
}

/** @phpstan-ignore arrayFilter.strict */
return \implode("\n\n", \array_filter($elements)) . "\n";
}

20 changes: 14 additions & 6 deletions src/Utils/TypeInfo.php
Original file line number Diff line number Diff line change
@@ -91,6 +91,7 @@ public function getFieldDefStack(): array
* ...
* ]
*
* @param (Type&NamedType)|(Type&WrappingType) $type
* @param array<string, Type&NamedType> $typeMap
*
* @throws InvariantViolation
@@ -103,9 +104,8 @@ public static function extractTypes(Type $type, array &$typeMap): void
return;
}

assert($type instanceof NamedType, 'only other option');

$name = $type->name;
\assert(\is_string($name));

if (isset($typeMap[$name])) {
if ($typeMap[$name] !== $type) {
@@ -127,7 +127,9 @@ public static function extractTypes(Type $type, array &$typeMap): void

if ($type instanceof InputObjectType) {
foreach ($type->getFields() as $field) {
self::extractTypes($field->getType(), $typeMap);
$fieldType = $field->getType();
\assert($fieldType instanceof NamedType || $fieldType instanceof WrappingType);
self::extractTypes($fieldType, $typeMap);
}

return;
@@ -142,10 +144,14 @@ public static function extractTypes(Type $type, array &$typeMap): void
if ($type instanceof HasFieldsType) {
foreach ($type->getFields() as $field) {
foreach ($field->args as $arg) {
self::extractTypes($arg->getType(), $typeMap);
$argType = $arg->getType();
\assert($argType instanceof NamedType || $argType instanceof WrappingType);
self::extractTypes($argType, $typeMap);
}

self::extractTypes($field->getType(), $typeMap);
$fieldType = $field->getType();
\assert($fieldType instanceof NamedType || $fieldType instanceof WrappingType);
self::extractTypes($fieldType, $typeMap);
}
}
}
@@ -158,7 +164,9 @@ public static function extractTypes(Type $type, array &$typeMap): void
public static function extractTypesFromDirectives(Directive $directive, array &$typeMap): void
{
foreach ($directive->args as $arg) {
self::extractTypes($arg->getType(), $typeMap);
$argType = $arg->getType();
\assert($argType instanceof NamedType || $argType instanceof WrappingType);
self::extractTypes($argType, $typeMap);
}
}

10 changes: 5 additions & 5 deletions src/Validator/Rules/OverlappingFieldsCanBeMerged.php
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@
*
* @phpstan-type ReasonOrReasons string|array<array{string, string|array<mixed>}>
* @phpstan-type Conflict array{array{string, ReasonOrReasons}, array<int, FieldNode>, array<int, FieldNode>}
* @phpstan-type FieldInfo array{Type, FieldNode, FieldDefinition|null}
* @phpstan-type FieldInfo array{Type|null, FieldNode, FieldDefinition|null}
* @phpstan-type FieldMap array<string, array<int, FieldInfo>>
*/
class OverlappingFieldsCanBeMerged extends ValidationRule
@@ -249,8 +249,8 @@ protected function internalCollectFieldsAndFragmentNames(
$fieldName = $selection->name->value;
$fieldDef = null;
if (
($parentType instanceof ObjectType
|| $parentType instanceof InterfaceType) && $parentType->hasField($fieldName)
($parentType instanceof ObjectType || $parentType instanceof InterfaceType)
&& $parentType->hasField($fieldName)
) {
$fieldDef = $parentType->getField($fieldName);
}
@@ -329,8 +329,8 @@ protected function collectConflictsWithin(
* Determines if there is a conflict between two particular fields, including
* comparing their sub-fields.
*
* @param array{Type, FieldNode, FieldDefinition|null} $field1
* @param array{Type, FieldNode, FieldDefinition|null} $field2
* @param array{Type|null, FieldNode, FieldDefinition|null} $field1
* @param array{Type|null, FieldNode, FieldDefinition|null} $field2
*
* @phpstan-return Conflict|null
*
2 changes: 1 addition & 1 deletion src/Validator/SDLValidationContext.php
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ class SDLValidationContext implements ValidationContext

protected ?Schema $schema;

/** @var array<int, Error> */
/** @var list<Error> */
protected array $errors = [];

public function __construct(DocumentNode $ast, ?Schema $schema)
15 changes: 10 additions & 5 deletions tests/Error/ErrorTest.php
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

use GraphQL\Error\Error;
use GraphQL\Error\FormattedError;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NullValueNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\Parser;
@@ -151,7 +152,8 @@ public function testDefaultErrorFormatterIncludesExtensionFields(): void
public function testErrorReadsOverriddenMethods(): void
{
$error = new class('msg', null, null, [], null, null, ['foo' => 'bar']) extends Error {
public function getExtensions(): ?array
/** @return array<string, mixed> */
public function getExtensions(): array
{
$extensions = parent::getExtensions();
$extensions['subfoo'] = 'subbar';
@@ -164,12 +166,13 @@ public function getPositions(): array
return [1 => 2];
}

public function getSource(): ?Source
public function getSource(): Source
{
return new Source('');
}

public function getNodes(): ?array
/** @return list<Node> */
public function getNodes(): array
{
return [];
}
@@ -183,12 +186,14 @@ public function getNodes(): ?array
self::assertNotNull($locatedError->getSource());

$error = new class('msg', new NullValueNode([]), null, []) extends Error {
public function getNodes(): ?array
/** @return list<NullValueNode> */
public function getNodes(): array
{
return [new NullValueNode([])];
}

public function getPath(): ?array
/** @return list<string> */
public function getPath(): array
{
return ['path'];
}
10 changes: 7 additions & 3 deletions tests/Executor/AbstractTest.php
Original file line number Diff line number Diff line change
@@ -718,16 +718,20 @@ public function testResolveTypeAllowsResolvingWithTypeName(): void

public function testHintsOnConflictingTypeInstancesInResolveType(): void
{
/** @var InterfaceType $iface */
/** @var InterfaceType|null $iface */
$iface = null;

$createTest = static function () use (&$iface): ObjectType {
$createTest = function () use (&$iface): ObjectType {
return new ObjectType([
'name' => 'Test',
'fields' => [
'a' => Type::string(),
],
'interfaces' => static fn (): array => [$iface],
'interfaces' => function () use ($iface): array {
self::assertNotNull($iface);

return [$iface];
},
]);
};

6 changes: 4 additions & 2 deletions tests/Executor/ExecutorTest.php
Original file line number Diff line number Diff line change
@@ -230,7 +230,7 @@ public function testProvidesInfoAboutCurrentExecutionState(): void
{
$ast = Parser::parse('query ($var: String) { result: test }');

/** @var ResolveInfo $info */
/** @var ResolveInfo|null $info */
$info = null;
$schema = new Schema([
'query' => new ObjectType([
@@ -250,6 +250,8 @@ public function testProvidesInfoAboutCurrentExecutionState(): void

Executor::execute($schema, $ast, $rootValue, null, ['var' => '123']);

self::assertNotNull($info);

/** @var OperationDefinitionNode $operationDefinition */
$operationDefinition = $ast->definitions[0];

@@ -327,7 +329,7 @@ public function testCorrectlyThreadsArguments(): void
]),
]);
Executor::execute($schema, $docAst, null, null, [], 'Example');
self::assertSame($gotHere, true);
self::assertTrue($gotHere);
}

public function testArgsMapper(): void
1 change: 0 additions & 1 deletion tests/Executor/Promise/ReactPromiseAdapterTest.php
Original file line number Diff line number Diff line change
@@ -39,7 +39,6 @@ public function setUp(): void

public function testIsThenableReturnsTrueWhenAReactPromiseIsGiven(): void
{
/** @var callable $reactPromiseSetRejectionHandler */
$reactPromiseSetRejectionHandler = \function_exists('\React\Promise\set_rejection_handler')
? '\React\Promise\set_rejection_handler'
: fn ($error) => null;
2 changes: 2 additions & 0 deletions tests/Executor/Promise/SyncPromiseTest.php
Original file line number Diff line number Diff line change
@@ -110,6 +110,7 @@ static function () use (&$onRejectedCalled): void {
self::assertNotSame($promise, $nextPromise);
self::assertSame(SyncPromise::PENDING, $nextPromise->state);
} else {
/** @phpstan-ignore argument.unresolvableType (false positive?) */
self::assertSame(SyncPromise::FULFILLED, $nextPromise->state);
}

@@ -282,6 +283,7 @@ static function () use (&$onFulfilledCalled): void {
self::assertNotSame($promise, $nextPromise);
self::assertSame(SyncPromise::PENDING, $nextPromise->state);
} else {
/** @phpstan-ignore argument.unresolvableType (false positive?) */
self::assertSame(SyncPromise::REJECTED, $nextPromise->state);
}

Original file line number Diff line number Diff line change
@@ -37,6 +37,6 @@ public function isStaticMethodSupported(MethodReflection $staticMethodReflection

public function specifyTypes(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
return $this->typeSpecifier->create($node->getArgs()[0]->value, new ObjectType(AbstractType::class), $context);
return $this->typeSpecifier->create($node->getArgs()[0]->value, new ObjectType(AbstractType::class), $context, $scope);
}
}
Original file line number Diff line number Diff line change
@@ -37,6 +37,6 @@ public function isStaticMethodSupported(MethodReflection $staticMethodReflection

public function specifyTypes(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
return $this->typeSpecifier->create($node->getArgs()[0]->value, new ObjectType(CompositeType::class), $context);
return $this->typeSpecifier->create($node->getArgs()[0]->value, new ObjectType(CompositeType::class), $context, $scope);
}
}
Original file line number Diff line number Diff line change
@@ -37,6 +37,6 @@ public function isStaticMethodSupported(MethodReflection $staticMethodReflection

public function specifyTypes(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
return $this->typeSpecifier->create($node->getArgs()[0]->value, new ObjectType(InputType::class), $context);
return $this->typeSpecifier->create($node->getArgs()[0]->value, new ObjectType(InputType::class), $context, $scope);
}
}
Original file line number Diff line number Diff line change
@@ -37,6 +37,6 @@ public function isStaticMethodSupported(MethodReflection $staticMethodReflection

public function specifyTypes(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
return $this->typeSpecifier->create($node->getArgs()[0]->value, new ObjectType(OutputType::class), $context);
return $this->typeSpecifier->create($node->getArgs()[0]->value, new ObjectType(OutputType::class), $context, $scope);
}
}
12 changes: 8 additions & 4 deletions tests/StarWarsSchema.php
Original file line number Diff line number Diff line change
@@ -85,10 +85,10 @@ public static function build(): Schema
],
]);

/** @var ObjectType $humanType */
/** @var ObjectType|null $humanType */
$humanType = null;

/** @var ObjectType $droidType */
/** @var ObjectType|null $droidType */
$droidType = null;

/**
@@ -136,10 +136,14 @@ public static function build(): Schema
],
];
},
'resolveType' => static function (array $obj) use (&$humanType, &$droidType): ObjectType {
return StarWarsData::human($obj['id']) === null
'resolveType' => function (array $obj) use (&$humanType, &$droidType): ObjectType {
$objectType = StarWarsData::human($obj['id']) === null
? $droidType
: $humanType;

assert($objectType !== null);

return $objectType;
},
]);

8 changes: 6 additions & 2 deletions tests/Type/QueryPlanTest.php
Original file line number Diff line number Diff line change
@@ -734,7 +734,7 @@ public function testMergedFragmentsQueryPlan(): void
];

$hasCalled = false;
/** @var QueryPlan $queryPlan */
/** @var QueryPlan|null $queryPlan */
$queryPlan = null;

$blogQuery = new ObjectType([
@@ -763,6 +763,8 @@ public function testMergedFragmentsQueryPlan(): void
$schema = new Schema(['query' => $blogQuery]);
$result = GraphQL::executeQuery($schema, $doc)->toArray();

self::assertNotNull($queryPlan);

self::assertTrue($hasCalled);
self::assertSame(['data' => ['article' => null]], $result);
self::assertEquals($expectedQueryPlan, $queryPlan->queryPlan());
@@ -961,7 +963,7 @@ public function testQueryPlanGroupingImplementorFieldsForAbstractTypes(): void
$expectedBuildingSubFields = ['city', 'address'];

$hasCalled = false;
/** @var QueryPlan $queryPlan */
/** @var QueryPlan|null $queryPlan */
$queryPlan = null;

$root = new ObjectType([
@@ -987,6 +989,8 @@ public function testQueryPlanGroupingImplementorFieldsForAbstractTypes(): void
]);
$result = GraphQL::executeQuery($schema, $query)->toArray();

self::assertNotNull($queryPlan);

self::assertTrue($hasCalled);
self::assertSame($expectedResult, $result);
self::assertSame($expectedQueryPlan, $queryPlan->queryPlan());
6 changes: 3 additions & 3 deletions tests/Utils/BuildSchemaTest.php
Original file line number Diff line number Diff line change
@@ -1451,15 +1451,15 @@ interface Hello {
self::assertSame(['Query'], $created);

$schema->getType('Color');
/** @var array<string> $created reset the type for PHPStan */
/** @phpstan-ignore staticMethod.impossibleType */
self::assertSame(['Query', 'Color'], $created);

$schema->getType('Hello');
/** @var array<string> $created reset the type for PHPStan */
/** @phpstan-ignore staticMethod.impossibleType */
self::assertSame(['Query', 'Color', 'Hello'], $created);

$types = $schema->getTypeMap();
/** @var array<string> $created reset the type for PHPStan */
/** @phpstan-ignore staticMethod.impossibleType */
self::assertSame(['Query', 'Color', 'Hello', 'World'], $created);

self::assertArrayHasKey('Query', $types);
2 changes: 1 addition & 1 deletion tests/Utils/SchemaExtenderTest/SomeInterfaceClassType.php
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ final class SomeInterfaceClassType extends InterfaceType
{
public ObjectType $concrete;

public function resolveType($objectValue, $context, ResolveInfo $info)
public function resolveType($objectValue, $context, ResolveInfo $info): ObjectType
{
return $this->concrete;
}
2 changes: 1 addition & 1 deletion tests/Utils/SchemaExtenderTest/SomeUnionClassType.php
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ final class SomeUnionClassType extends UnionType
{
public ObjectType $concrete;

public function resolveType($objectValue, $context, ResolveInfo $info)
public function resolveType($objectValue, $context, ResolveInfo $info): ObjectType
{
return $this->concrete;
}