diff --git a/composer.json b/composer.json index 206ebebb5..0561bd68b 100644 --- a/composer.json +++ b/composer.json @@ -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" diff --git a/generate-class-reference.php b/generate-class-reference.php index 0dc33772a..dff2bace2 100644 --- a/generate-class-reference.php +++ b/generate-class-reference.php @@ -39,7 +39,7 @@ ]; /** - * @param ReflectionClass $class + * @param ReflectionClass $class * @param array{constants?: bool, props?: bool, methods?: bool} $options * * @throws ExceptionInterface diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c9ad1df81..23c37bd4b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -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\\\\) does not accept array\\\\.$#" + message: '#^Property GraphQL\\Type\\Definition\\InterfaceType\:\:\$interfaces \(array\\) does not accept array\\.$#' + 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\\\\) does not accept array\\\\.$#" + message: '#^Property GraphQL\\Type\\Definition\\ObjectType\:\:\$interfaces \(array\\) does not accept array\\.$#' + 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\\ but returns array\\\\.$#" + message: '#^Method GraphQL\\Type\\Definition\\UnionType\:\:getTypes\(\) should return array\ but returns array\\.$#' + identifier: return.type count: 1 path: src/Type/Definition/UnionType.php - - message: "#^Property GraphQL\\\\Type\\\\Definition\\\\UnionType\\:\\:\\$types \\(array\\\\) does not accept array\\\\.$#" + message: '#^Property GraphQL\\Type\\Definition\\UnionType\:\:\$types \(array\\) does not accept array\\.$#' + 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\\\\>, array\\\\} but returns array\\{mixed, array\\\\}\\.$#" - count: 1 - path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php - - - - message: "#^SplObjectStorage\\\\>, array\\\\}\\> does not accept array\\\\.$#" - count: 1 - path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php \ No newline at end of file diff --git a/phpstan/include-by-php-version.php b/phpstan/include-by-php-version.php index a9fa1354e..1e6e5fc35 100644 --- a/phpstan/include-by-php-version.php +++ b/phpstan/include-by-php-version.php @@ -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'; } diff --git a/phpstan/php-below-8.2.neon b/phpstan/php-below-8.2.neon new file mode 100644 index 000000000..3ad9d9536 --- /dev/null +++ b/phpstan/php-below-8.2.neon @@ -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 diff --git a/phpstan/php-below-8.4.neon b/phpstan/php-below-8.4.neon new file mode 100644 index 000000000..bd3220b60 --- /dev/null +++ b/phpstan/php-below-8.4.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: '#^Call\-site variance of covariant object in generic type ReflectionClass\ 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 diff --git a/src/Error/Error.php b/src/Error/Error.php index 3491610ac..0e2a741c3 100644 --- a/src/Error/Error.php +++ b/src/Error/Error.php @@ -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); diff --git a/src/Executor/ExecutionContext.php b/src/Executor/ExecutionContext.php index a46de2753..4450b61d3 100644 --- a/src/Executor/ExecutionContext.php +++ b/src/Executor/ExecutionContext.php @@ -49,7 +49,7 @@ class ExecutionContext */ public $argsMapper; - /** @var array */ + /** @var list */ public array $errors; public PromiseAdapter $promiseAdapter; @@ -59,7 +59,7 @@ class ExecutionContext * @param mixed $rootValue * @param mixed $contextValue * @param array $variableValues - * @param array $errors + * @param list $errors * * @phpstan-param FieldResolver $fieldResolver */ diff --git a/src/Executor/ExecutionResult.php b/src/Executor/ExecutionResult.php index f48a53d0d..1756e5e52 100644 --- a/src/Executor/ExecutionResult.php +++ b/src/Executor/ExecutionResult.php @@ -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) diff --git a/src/Executor/Promise/Adapter/AmpPromiseAdapter.php b/src/Executor/Promise/Adapter/AmpPromiseAdapter.php index c05188b72..2d4fa34f6 100644 --- a/src/Executor/Promise/Adapter/AmpPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/AmpPromiseAdapter.php @@ -126,7 +126,7 @@ public function all(iterable $promisesOrValues): Promise /** * @template TArgument - * @template TResult + * @template TResult of AmpPromise * * @param Deferred $deferred * @param callable(TArgument): TResult $callback diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index c9c8ad202..854a149b3 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -150,7 +150,7 @@ public function doExecute(): Promise * * @throws \Exception * - * @return ExecutionContext|array + * @return ExecutionContext|list */ protected static function buildExecutionContext( Schema $schema, @@ -163,7 +163,7 @@ protected static function buildExecutionContext( callable $argsMapper, PromiseAdapter $promiseAdapter ) { - /** @var array $errors */ + /** @var list $errors */ $errors = []; /** @var array $fragments */ diff --git a/src/Language/AST/Node.php b/src/Language/AST/Node.php index cb827c260..dad7c8502 100644 --- a/src/Language/AST/Node.php +++ b/src/Language/AST/Node.php @@ -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 as potentially different from TCloneable) + */ return $value->cloneDeep(); } diff --git a/src/Language/AST/NodeList.php b/src/Language/AST/NodeList.php index 01071d476..2ba76d08a 100644 --- a/src/Language/AST/NodeList.php +++ b/src/Language/AST/NodeList.php @@ -145,8 +145,9 @@ public function reindex(): void */ public function cloneDeep(): self { - /** @var static $cloned */ - $cloned = new static([]); + /** @var array $empty */ + $empty = []; + $cloned = new static($empty); foreach ($this->getIterator() as $key => $node) { $cloned[$key] = $node->cloneDeep(); } diff --git a/src/Language/Printer.php b/src/Language/Printer.php index 884b51f6e..9d2e89392 100644 --- a/src/Language/Printer.php +++ b/src/Language/Printer.php @@ -531,6 +531,6 @@ protected function indent(string $string): string /** @param array $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)); } } diff --git a/src/Language/Visitor.php b/src/Language/Visitor.php index ad8892c7e..c8dc676b9 100644 --- a/src/Language/Visitor.php +++ b/src/Language/Visitor.php @@ -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 $ancestors): VisitorOperation|Node|null + * @return (callable(Node $node, string $key, Node|NodeList|null $parent, array $path, array> $ancestors): (VisitorOperation|Node|null))|(callable(Node): (VisitorOperation|void|false|null))|null */ protected static function extractVisitFn(array $visitor, string $kind, bool $isLeaving): ?callable { diff --git a/src/Type/Definition/Argument.php b/src/Type/Definition/Argument.php index ce874b410..3e6f40b4d 100644 --- a/src/Type/Definition/Argument.php +++ b/src/Type/Definition/Argument.php @@ -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; diff --git a/src/Type/Definition/QueryPlan.php b/src/Type/Definition/QueryPlan.php index 1605b565d..51c9534a1 100644 --- a/src/Type/Definition/QueryPlan.php +++ b/src/Type/Definition/QueryPlan.php @@ -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) ), ]; diff --git a/src/Utils/AST.php b/src/Utils/AST.php index 2e844a871..eca6c14e2 100644 --- a/src/Utils/AST.php +++ b/src/Utils/AST.php @@ -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]); } diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index 886c4c8b3..dd3a2cbf8 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -174,7 +174,7 @@ private function makeInputValues(NodeList $values): array */ private function makeInputFields(array $nodes): array { - /** @var array> $fields */ + /** @var array $fields */ $fields = []; foreach ($nodes as $node) { \array_push($fields, ...$node->fields); diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index 54b78b7a5..b2aab509e 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -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; } diff --git a/src/Utils/SchemaPrinter.php b/src/Utils/SchemaPrinter.php index a98485649..59609bbe3 100644 --- a/src/Utils/SchemaPrinter.php +++ b/src/Utils/SchemaPrinter.php @@ -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"; } diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 4a323db5f..7e23435e6 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -91,6 +91,7 @@ public function getFieldDefStack(): array * ... * ] * + * @param (Type&NamedType)|(Type&WrappingType) $type * @param array $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); } } diff --git a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php index 41f408ab4..d63ba8f5a 100644 --- a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php +++ b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php @@ -28,7 +28,7 @@ * * @phpstan-type ReasonOrReasons string|array}> * @phpstan-type Conflict array{array{string, ReasonOrReasons}, array, array} - * @phpstan-type FieldInfo array{Type, FieldNode, FieldDefinition|null} + * @phpstan-type FieldInfo array{Type|null, FieldNode, FieldDefinition|null} * @phpstan-type FieldMap array> */ 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 * diff --git a/src/Validator/SDLValidationContext.php b/src/Validator/SDLValidationContext.php index eecba85d2..6d39e6c43 100644 --- a/src/Validator/SDLValidationContext.php +++ b/src/Validator/SDLValidationContext.php @@ -12,7 +12,7 @@ class SDLValidationContext implements ValidationContext protected ?Schema $schema; - /** @var array */ + /** @var list */ protected array $errors = []; public function __construct(DocumentNode $ast, ?Schema $schema) diff --git a/tests/Error/ErrorTest.php b/tests/Error/ErrorTest.php index ba8a7ab2f..c71d8c050 100644 --- a/tests/Error/ErrorTest.php +++ b/tests/Error/ErrorTest.php @@ -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 */ + 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 */ + 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 */ + public function getNodes(): array { return [new NullValueNode([])]; } - public function getPath(): ?array + /** @return list */ + public function getPath(): array { return ['path']; } diff --git a/tests/Executor/AbstractTest.php b/tests/Executor/AbstractTest.php index 3c086e224..850ddce8a 100644 --- a/tests/Executor/AbstractTest.php +++ b/tests/Executor/AbstractTest.php @@ -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]; + }, ]); }; diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index 4538f41f7..230c6d731 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -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 diff --git a/tests/Executor/Promise/ReactPromiseAdapterTest.php b/tests/Executor/Promise/ReactPromiseAdapterTest.php index c1e14f051..350821b2b 100644 --- a/tests/Executor/Promise/ReactPromiseAdapterTest.php +++ b/tests/Executor/Promise/ReactPromiseAdapterTest.php @@ -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; diff --git a/tests/Executor/Promise/SyncPromiseTest.php b/tests/Executor/Promise/SyncPromiseTest.php index 4fd830466..734d7dc7b 100644 --- a/tests/Executor/Promise/SyncPromiseTest.php +++ b/tests/Executor/Promise/SyncPromiseTest.php @@ -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); } diff --git a/tests/PhpStan/Type/Definition/Type/IsAbstractTypeStaticMethodTypeSpecifyingExtension.php b/tests/PhpStan/Type/Definition/Type/IsAbstractTypeStaticMethodTypeSpecifyingExtension.php index e299523f7..e4a6aab9d 100644 --- a/tests/PhpStan/Type/Definition/Type/IsAbstractTypeStaticMethodTypeSpecifyingExtension.php +++ b/tests/PhpStan/Type/Definition/Type/IsAbstractTypeStaticMethodTypeSpecifyingExtension.php @@ -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); } } diff --git a/tests/PhpStan/Type/Definition/Type/IsCompositeTypeStaticMethodTypeSpecifyingExtension.php b/tests/PhpStan/Type/Definition/Type/IsCompositeTypeStaticMethodTypeSpecifyingExtension.php index 508331aa4..54eaf8560 100644 --- a/tests/PhpStan/Type/Definition/Type/IsCompositeTypeStaticMethodTypeSpecifyingExtension.php +++ b/tests/PhpStan/Type/Definition/Type/IsCompositeTypeStaticMethodTypeSpecifyingExtension.php @@ -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); } } diff --git a/tests/PhpStan/Type/Definition/Type/IsInputTypeStaticMethodTypeSpecifyingExtension.php b/tests/PhpStan/Type/Definition/Type/IsInputTypeStaticMethodTypeSpecifyingExtension.php index 74a133297..d4bb5cd45 100644 --- a/tests/PhpStan/Type/Definition/Type/IsInputTypeStaticMethodTypeSpecifyingExtension.php +++ b/tests/PhpStan/Type/Definition/Type/IsInputTypeStaticMethodTypeSpecifyingExtension.php @@ -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); } } diff --git a/tests/PhpStan/Type/Definition/Type/IsOutputTypeStaticMethodTypeSpecifyingExtension.php b/tests/PhpStan/Type/Definition/Type/IsOutputTypeStaticMethodTypeSpecifyingExtension.php index 6bed2f8a5..3dec70656 100644 --- a/tests/PhpStan/Type/Definition/Type/IsOutputTypeStaticMethodTypeSpecifyingExtension.php +++ b/tests/PhpStan/Type/Definition/Type/IsOutputTypeStaticMethodTypeSpecifyingExtension.php @@ -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); } } diff --git a/tests/StarWarsSchema.php b/tests/StarWarsSchema.php index 913cc1c16..e0689157c 100644 --- a/tests/StarWarsSchema.php +++ b/tests/StarWarsSchema.php @@ -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; }, ]); diff --git a/tests/Type/QueryPlanTest.php b/tests/Type/QueryPlanTest.php index 01410e519..b386bfc0f 100644 --- a/tests/Type/QueryPlanTest.php +++ b/tests/Type/QueryPlanTest.php @@ -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()); diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index 8867ec7cd..af8d09521 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -1451,15 +1451,15 @@ interface Hello { self::assertSame(['Query'], $created); $schema->getType('Color'); - /** @var array $created reset the type for PHPStan */ + /** @phpstan-ignore staticMethod.impossibleType */ self::assertSame(['Query', 'Color'], $created); $schema->getType('Hello'); - /** @var array $created reset the type for PHPStan */ + /** @phpstan-ignore staticMethod.impossibleType */ self::assertSame(['Query', 'Color', 'Hello'], $created); $types = $schema->getTypeMap(); - /** @var array $created reset the type for PHPStan */ + /** @phpstan-ignore staticMethod.impossibleType */ self::assertSame(['Query', 'Color', 'Hello', 'World'], $created); self::assertArrayHasKey('Query', $types); diff --git a/tests/Utils/SchemaExtenderTest/SomeInterfaceClassType.php b/tests/Utils/SchemaExtenderTest/SomeInterfaceClassType.php index a629ad6c5..df7bb25cd 100644 --- a/tests/Utils/SchemaExtenderTest/SomeInterfaceClassType.php +++ b/tests/Utils/SchemaExtenderTest/SomeInterfaceClassType.php @@ -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; } diff --git a/tests/Utils/SchemaExtenderTest/SomeUnionClassType.php b/tests/Utils/SchemaExtenderTest/SomeUnionClassType.php index dca4f8004..b3cc7243d 100644 --- a/tests/Utils/SchemaExtenderTest/SomeUnionClassType.php +++ b/tests/Utils/SchemaExtenderTest/SomeUnionClassType.php @@ -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; }