diff --git a/CHANGELOG.md b/CHANGELOG.md index d2f87300f..080fa0b4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ You can find and compare releases at the [GitHub release page](https://github.co ## Unreleased +### Changed + +- Differentiate between const and non-const AST nodes + ## v15.2.3 ### Fixed diff --git a/docs/class-reference.md b/docs/class-reference.md index a604e6a57..c36ea19b9 100644 --- a/docs/class-reference.md +++ b/docs/class-reference.md @@ -871,6 +871,10 @@ allowLegacySDLImplementsInterfaces?: bool, experimentalFragmentVariables?: bool } +@phpstan-import-type ConstValueNodeVariants from ConstValueNode +@phpstan-import-type ValueNodeVariants from ValueNode +@phpstan-import-type TypeNodeVariants from TypeNode + noLocation: (By default, the parser creates AST nodes that know the location in the source that they correspond to. This configuration flag @@ -926,17 +930,17 @@ Those magic functions allow partial parsing: @method static FragmentSpreadNode|InlineFragmentNode fragment(Source|string $source, bool[] $options = []) @method static FragmentDefinitionNode fragmentDefinition(Source|string $source, bool[] $options = []) @method static NameNode fragmentName(Source|string $source, bool[] $options = []) -@method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|NullValueNode|ObjectValueNode|StringValueNode|VariableNode valueLiteral(Source|string $source, bool[] $options = []) -@method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|NullValueNode|ObjectValueNode|StringValueNode constValueLiteral(Source|string $source, bool[] $options = []) +@method static ValueNodeVariants valueLiteral(Source|string $source, bool[] $options = []) +@method static ConstValueNodeVariants constValueLiteral(Source|string $source, bool[] $options = []) @method static StringValueNode stringLiteral(Source|string $source, bool[] $options = []) -@method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode constValue(Source|string $source, bool[] $options = []) -@method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode variableValue(Source|string $source, bool[] $options = []) +@method static ConstValueNodeVariants constValue(Source|string $source, bool[] $options = []) +@method static ValueNodeVariants variableValue(Source|string $source, bool[] $options = []) @method static ListValueNode array(Source|string $source, bool[] $options = []) -@method static ListValueNode constArray(Source|string $source, bool[] $options = []) +@method static ConstListValueNode constArray(Source|string $source, bool[] $options = []) @method static ObjectValueNode object(Source|string $source, bool[] $options = []) -@method static ObjectValueNode constObject(Source|string $source, bool[] $options = []) +@method static ConstObjectValueNode constObject(Source|string $source, bool[] $options = []) @method static ObjectFieldNode objectField(Source|string $source, bool[] $options = []) -@method static ObjectFieldNode constObjectField(Source|string $source, bool[] $options = []) +@method static ConstObjectFieldNode constObjectField(Source|string $source, bool[] $options = []) @method static NodeList directives(Source|string $source, bool[] $options = []) @method static NodeList constDirectives(Source|string $source, bool[] $options = []) @method static DirectiveNode directive(Source|string $source, bool[] $options = []) @@ -1012,11 +1016,11 @@ static function parse($source, array $options = []): GraphQL\Language\AST\Docume * @throws \JsonException * @throws SyntaxError * - * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|NullValueNode|ObjectValueNode|StringValueNode|VariableNode + * @return ValueNodeVariants * * @api */ -static function parseValue($source, array $options = []) +static function parseValue($source, array $options = []): GraphQL\Language\AST\ValueNode ``` ```php @@ -1037,11 +1041,11 @@ static function parseValue($source, array $options = []) * @throws \JsonException * @throws SyntaxError * - * @return ListTypeNode|NamedTypeNode|NonNullTypeNode + * @return TypeNodeVariants * * @api */ -static function parseType($source, array $options = []) +static function parseType($source, array $options = []): GraphQL\Language\AST\TypeNode ``` ## GraphQL\Language\Printer @@ -2534,7 +2538,7 @@ static function astFromValue($value, GraphQL\Type\Definition\InputType $type): ? * | Enum Value | Mixed | * | Null Value | null | * - * @param (ValueNode&Node)|null $valueNode + * @param (ValueNode&Node)|(ConstValueNode&Node)|null $valueNode * @param array|null $variables * * @throws \Exception @@ -2544,7 +2548,7 @@ static function astFromValue($value, GraphQL\Type\Definition\InputType $type): ? * @api */ static function valueFromAST( - ?GraphQL\Language\AST\ValueNode $valueNode, + ?GraphQL\Language\AST\Node $valueNode, GraphQL\Type\Definition\Type $type, ?array $variables = null ) diff --git a/src/Executor/Values.php b/src/Executor/Values.php index e1d40b0dc..2f254a80c 100644 --- a/src/Executor/Values.php +++ b/src/Executor/Values.php @@ -3,7 +3,6 @@ namespace GraphQL\Executor; use GraphQL\Error\Error; -use GraphQL\Language\AST\ArgumentNode; use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\EnumTypeDefinitionNode; use GraphQL\Language\AST\EnumTypeExtensionNode; @@ -29,6 +28,7 @@ use GraphQL\Language\AST\SchemaExtensionNode; use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\AST\UnionTypeExtensionNode; +use GraphQL\Language\AST\ValueNode; use GraphQL\Language\AST\VariableDefinitionNode; use GraphQL\Language\AST\VariableNode; use GraphQL\Language\Printer; @@ -42,9 +42,7 @@ use GraphQL\Utils\Value; /** - * @see ArgumentNode - force IDE import - * - * @phpstan-import-type ArgumentNodeValue from ArgumentNode + * @phpstan-import-type ValueNodeVariants from ValueNode */ class Values { @@ -184,7 +182,7 @@ public static function getArgumentValues($def, Node $node, ?array $variableValue return []; } - /** @var array $argumentValueMap */ + /** @var array $argumentValueMap */ $argumentValueMap = []; // Might not be defined when an AST from JS is used @@ -199,7 +197,7 @@ public static function getArgumentValues($def, Node $node, ?array $variableValue /** * @param FieldDefinition|Directive $def - * @param array $argumentValueMap + * @param array $argumentValueMap * @param array|null $variableValues * * @throws \Exception diff --git a/src/Language/AST/ArgumentNode.php b/src/Language/AST/ArgumentNode.php index 6add5032b..6871a4d46 100644 --- a/src/Language/AST/ArgumentNode.php +++ b/src/Language/AST/ArgumentNode.php @@ -3,13 +3,13 @@ namespace GraphQL\Language\AST; /** - * @phpstan-type ArgumentNodeValue VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode + * @phpstan-import-type ValueNodeVariants from ValueNode */ class ArgumentNode extends Node { public string $kind = NodeKind::ARGUMENT; - /** @phpstan-var ArgumentNodeValue */ + /** @var ValueNodeVariants */ public ValueNode $value; public NameNode $name; diff --git a/src/Language/AST/BooleanValueNode.php b/src/Language/AST/BooleanValueNode.php index 4d987d97f..f89cb7b7e 100644 --- a/src/Language/AST/BooleanValueNode.php +++ b/src/Language/AST/BooleanValueNode.php @@ -2,7 +2,7 @@ namespace GraphQL\Language\AST; -class BooleanValueNode extends Node implements ValueNode +class BooleanValueNode extends Node implements ValueNode, ConstValueNode { public string $kind = NodeKind::BOOLEAN; diff --git a/src/Language/AST/ConstArgumentNode.php b/src/Language/AST/ConstArgumentNode.php new file mode 100644 index 000000000..6e3d642dc --- /dev/null +++ b/src/Language/AST/ConstArgumentNode.php @@ -0,0 +1,16 @@ + */ + public NodeList $values; +} diff --git a/src/Language/AST/ConstObjectFieldNode.php b/src/Language/AST/ConstObjectFieldNode.php new file mode 100644 index 000000000..a5b217b75 --- /dev/null +++ b/src/Language/AST/ConstObjectFieldNode.php @@ -0,0 +1,16 @@ + */ + public NodeList $fields; +} diff --git a/src/Language/AST/ConstValueNode.php b/src/Language/AST/ConstValueNode.php new file mode 100644 index 000000000..e5a6c49d6 --- /dev/null +++ b/src/Language/AST/ConstValueNode.php @@ -0,0 +1,10 @@ + */ diff --git a/src/Language/AST/IntValueNode.php b/src/Language/AST/IntValueNode.php index 3d458d3e7..235a8e8b6 100644 --- a/src/Language/AST/IntValueNode.php +++ b/src/Language/AST/IntValueNode.php @@ -2,7 +2,7 @@ namespace GraphQL\Language\AST; -class IntValueNode extends Node implements ValueNode +class IntValueNode extends Node implements ValueNode, ConstValueNode { public string $kind = NodeKind::INT; diff --git a/src/Language/AST/ListValueNode.php b/src/Language/AST/ListValueNode.php index 323f62d8d..cf2f342d9 100644 --- a/src/Language/AST/ListValueNode.php +++ b/src/Language/AST/ListValueNode.php @@ -2,7 +2,7 @@ namespace GraphQL\Language\AST; -class ListValueNode extends Node implements ValueNode +class ListValueNode extends Node implements ValueNode, ConstValueNode { public string $kind = NodeKind::LST; diff --git a/src/Language/AST/NullValueNode.php b/src/Language/AST/NullValueNode.php index 6ca71e11b..5b56eae92 100644 --- a/src/Language/AST/NullValueNode.php +++ b/src/Language/AST/NullValueNode.php @@ -2,7 +2,7 @@ namespace GraphQL\Language\AST; -class NullValueNode extends Node implements ValueNode +class NullValueNode extends Node implements ValueNode, ConstValueNode { public string $kind = NodeKind::NULL; } diff --git a/src/Language/AST/ObjectFieldNode.php b/src/Language/AST/ObjectFieldNode.php index 462a23a7d..1edc18241 100644 --- a/src/Language/AST/ObjectFieldNode.php +++ b/src/Language/AST/ObjectFieldNode.php @@ -2,12 +2,15 @@ namespace GraphQL\Language\AST; +/** + * @phpstan-import-type ValueNodeVariants from ValueNode + */ class ObjectFieldNode extends Node { public string $kind = NodeKind::OBJECT_FIELD; public NameNode $name; - /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode */ + /** @var ValueNodeVariants */ public ValueNode $value; } diff --git a/src/Language/AST/ObjectValueNode.php b/src/Language/AST/ObjectValueNode.php index d5f50c572..36190283d 100644 --- a/src/Language/AST/ObjectValueNode.php +++ b/src/Language/AST/ObjectValueNode.php @@ -2,7 +2,7 @@ namespace GraphQL\Language\AST; -class ObjectValueNode extends Node implements ValueNode +class ObjectValueNode extends Node implements ValueNode, ConstValueNode { public string $kind = NodeKind::OBJECT; diff --git a/src/Language/AST/StringValueNode.php b/src/Language/AST/StringValueNode.php index ca3e75189..e3c9f64c8 100644 --- a/src/Language/AST/StringValueNode.php +++ b/src/Language/AST/StringValueNode.php @@ -2,7 +2,7 @@ namespace GraphQL\Language\AST; -class StringValueNode extends Node implements ValueNode +class StringValueNode extends Node implements ValueNode, ConstValueNode { public string $kind = NodeKind::STRING; diff --git a/src/Language/AST/TypeNode.php b/src/Language/AST/TypeNode.php index a3abf211f..6f472e200 100644 --- a/src/Language/AST/TypeNode.php +++ b/src/Language/AST/TypeNode.php @@ -3,9 +3,7 @@ namespace GraphQL\Language\AST; /** - * export type TypeNode = NamedTypeNode - * | ListTypeNode - * | NonNullTypeNode. + * @phpstan-type TypeNodeVariants NamedTypeNode|ListTypeNode|NonNullTypeNode */ interface TypeNode { diff --git a/src/Language/AST/ValueNode.php b/src/Language/AST/ValueNode.php index 8bd43281b..58d8c5bd6 100644 --- a/src/Language/AST/ValueNode.php +++ b/src/Language/AST/ValueNode.php @@ -3,15 +3,7 @@ namespace GraphQL\Language\AST; /** -export type ValueNode = VariableNode -| NullValueNode -| IntValueNode -| FloatValueNode -| StringValueNode -| BooleanValueNode -| EnumValueNode -| ListValueNode -| ObjectValueNode + * @phpstan-type ValueNodeVariants VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode */ interface ValueNode { diff --git a/src/Language/AST/VariableDefinitionNode.php b/src/Language/AST/VariableDefinitionNode.php index 608ad282b..75f60ec55 100644 --- a/src/Language/AST/VariableDefinitionNode.php +++ b/src/Language/AST/VariableDefinitionNode.php @@ -2,6 +2,9 @@ namespace GraphQL\Language\AST; +/** + * @phpstan-import-type ConstValueNodeVariants from ConstValueNode + */ class VariableDefinitionNode extends Node implements DefinitionNode { public string $kind = NodeKind::VARIABLE_DEFINITION; @@ -11,8 +14,8 @@ class VariableDefinitionNode extends Node implements DefinitionNode /** @var NamedTypeNode|ListTypeNode|NonNullTypeNode */ public TypeNode $type; - /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null */ - public ?ValueNode $defaultValue = null; + /** @var ConstValueNodeVariants|null */ + public ?ConstValueNode $defaultValue = null; /** @var NodeList */ public NodeList $directives; diff --git a/src/Language/Parser.php b/src/Language/Parser.php index 985b259cf..5de5def95 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -5,6 +5,10 @@ use GraphQL\Error\SyntaxError; use GraphQL\Language\AST\ArgumentNode; use GraphQL\Language\AST\BooleanValueNode; +use GraphQL\Language\AST\ConstListValueNode; +use GraphQL\Language\AST\ConstObjectFieldNode; +use GraphQL\Language\AST\ConstObjectValueNode; +use GraphQL\Language\AST\ConstValueNode; use GraphQL\Language\AST\DefinitionNode; use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\DirectiveNode; @@ -68,6 +72,10 @@ * experimentalFragmentVariables?: bool * } * + * @phpstan-import-type ConstValueNodeVariants from ConstValueNode + * @phpstan-import-type ValueNodeVariants from ValueNode + * @phpstan-import-type TypeNodeVariants from TypeNode + * * noLocation: * (By default, the parser creates AST nodes that know the location * in the source that they correspond to. This configuration flag @@ -123,17 +131,17 @@ * @method static FragmentSpreadNode|InlineFragmentNode fragment(Source|string $source, bool[] $options = []) * @method static FragmentDefinitionNode fragmentDefinition(Source|string $source, bool[] $options = []) * @method static NameNode fragmentName(Source|string $source, bool[] $options = []) - * @method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|NullValueNode|ObjectValueNode|StringValueNode|VariableNode valueLiteral(Source|string $source, bool[] $options = []) - * @method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|NullValueNode|ObjectValueNode|StringValueNode constValueLiteral(Source|string $source, bool[] $options = []) + * @method static ValueNodeVariants valueLiteral(Source|string $source, bool[] $options = []) + * @method static ConstValueNodeVariants constValueLiteral(Source|string $source, bool[] $options = []) * @method static StringValueNode stringLiteral(Source|string $source, bool[] $options = []) - * @method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode constValue(Source|string $source, bool[] $options = []) - * @method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode variableValue(Source|string $source, bool[] $options = []) + * @method static ConstValueNodeVariants constValue(Source|string $source, bool[] $options = []) + * @method static ValueNodeVariants variableValue(Source|string $source, bool[] $options = []) * @method static ListValueNode array(Source|string $source, bool[] $options = []) - * @method static ListValueNode constArray(Source|string $source, bool[] $options = []) + * @method static ConstListValueNode constArray(Source|string $source, bool[] $options = []) * @method static ObjectValueNode object(Source|string $source, bool[] $options = []) - * @method static ObjectValueNode constObject(Source|string $source, bool[] $options = []) + * @method static ConstObjectValueNode constObject(Source|string $source, bool[] $options = []) * @method static ObjectFieldNode objectField(Source|string $source, bool[] $options = []) - * @method static ObjectFieldNode constObjectField(Source|string $source, bool[] $options = []) + * @method static ConstObjectFieldNode constObjectField(Source|string $source, bool[] $options = []) * @method static NodeList directives(Source|string $source, bool[] $options = []) * @method static NodeList constDirectives(Source|string $source, bool[] $options = []) * @method static DirectiveNode directive(Source|string $source, bool[] $options = []) @@ -209,17 +217,18 @@ public static function parse($source, array $options = []): DocumentNode * @throws \JsonException * @throws SyntaxError * - * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|NullValueNode|ObjectValueNode|StringValueNode|VariableNode + * @return ValueNodeVariants * * @api */ - public static function parseValue($source, array $options = []) + public static function parseValue($source, array $options = []): ValueNode { $parser = new Parser($source, $options); $parser->expect(Token::SOF); $value = $parser->parseValueLiteral(false); $parser->expect(Token::EOF); + // @phpstan-ignore-next-line isConst false makes sure this is never a ConstValueNode return $value; } @@ -240,11 +249,11 @@ public static function parseValue($source, array $options = []) * @throws \JsonException * @throws SyntaxError * - * @return ListTypeNode|NamedTypeNode|NonNullTypeNode + * @return TypeNodeVariants * * @api */ - public static function parseType($source, array $options = []) + public static function parseType($source, array $options = []): TypeNode { $parser = new Parser($source, $options); $parser->expect(Token::SOF); @@ -911,9 +920,9 @@ private function parseFragmentName(): NameNode * @throws \JsonException * @throws SyntaxError * - * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode|VariableNode|ListValueNode|ObjectValueNode|NullValueNode + * @return ValueNodeVariants|ConstValueNodeVariants */ - private function parseValueLiteral(bool $isConst): ValueNode + private function parseValueLiteral(bool $isConst): Node { $token = $this->lexer->token; switch ($token->kind) { @@ -998,8 +1007,9 @@ private function parseStringLiteral(): StringValueNode * @throws \JsonException * @throws SyntaxError */ - private function parseConstValue(): ValueNode + private function parseConstValue(): ConstValueNode { + // @phpstan-ignore-next-line return type depends on argument return $this->parseValueLiteral(true); } @@ -1015,15 +1025,20 @@ private function parseVariableValue(): ValueNode /** * @throws \JsonException * @throws SyntaxError + * + * @return ListValueNode|ConstListValueNode */ - private function parseArray(bool $isConst): ListValueNode + private function parseArray(bool $isConst): Node { $start = $this->lexer->token; $parseFn = $isConst - ? fn (): ValueNode => $this->parseConstValue() + ? fn (): ConstValueNode => $this->parseConstValue() : fn (): ValueNode => $this->parseVariableValue(); + $class = $isConst + ? ConstListValueNode::class + : ListValueNode::class; - return new ListValueNode([ + return new $class([ 'values' => $this->any(Token::BRACKET_L, $parseFn, Token::BRACKET_R), 'loc' => $this->loc($start), ]); @@ -1032,8 +1047,10 @@ private function parseArray(bool $isConst): ListValueNode /** * @throws \JsonException * @throws SyntaxError + * + * @return ObjectValueNode|ConstObjectValueNode */ - private function parseObject(bool $isConst): ObjectValueNode + private function parseObject(bool $isConst): Node { $start = $this->lexer->token; $this->expect(Token::BRACE_L); @@ -1041,8 +1058,11 @@ private function parseObject(bool $isConst): ObjectValueNode while (! $this->skip(Token::BRACE_R)) { $fields[] = $this->parseObjectField($isConst); } + $class = $isConst + ? ConstObjectValueNode::class + : ObjectValueNode::class; - return new ObjectValueNode([ + return new $class([ 'fields' => new NodeList($fields), 'loc' => $this->loc($start), ]); @@ -1051,15 +1071,21 @@ private function parseObject(bool $isConst): ObjectValueNode /** * @throws \JsonException * @throws SyntaxError + * + * @return ObjectFieldNode|ConstObjectFieldNode */ - private function parseObjectField(bool $isConst): ObjectFieldNode + private function parseObjectField(bool $isConst): Node { $start = $this->lexer->token; $name = $this->parseName(); $this->expect(Token::COLON); - return new ObjectFieldNode([ + $class = $isConst + ? ConstObjectFieldNode::class + : ObjectFieldNode::class; + + return new $class([ 'name' => $name, 'value' => $this->parseValueLiteral($isConst), 'loc' => $this->loc($start), @@ -1108,7 +1134,7 @@ private function parseDirective(bool $isConst): DirectiveNode * @throws \JsonException * @throws SyntaxError * - * @return ListTypeNode|NamedTypeNode|NonNullTypeNode + * @return TypeNodeVariants */ private function parseTypeReference(): TypeNode { diff --git a/src/Language/Printer.php b/src/Language/Printer.php index 8fa764714..103e1d78a 100644 --- a/src/Language/Printer.php +++ b/src/Language/Printer.php @@ -4,6 +4,9 @@ use GraphQL\Language\AST\ArgumentNode; use GraphQL\Language\AST\BooleanValueNode; +use GraphQL\Language\AST\ConstListValueNode; +use GraphQL\Language\AST\ConstObjectFieldNode; +use GraphQL\Language\AST\ConstObjectValueNode; use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\DocumentNode; @@ -92,7 +95,7 @@ public function printAST(Node $node): string /** * @throws \JsonException */ - protected function p(?Node $node, bool $isDescription = false): string + protected function p(?Node $node): string { if ($node === null) { return ''; @@ -317,6 +320,8 @@ protected function p(?Node $node, bool $isDescription = false): string return '[' . $this->p($node->type) . ']'; case $node instanceof ListValueNode: + case $node instanceof ConstListValueNode: + // @phpstan-ignore-next-line weird generic issue return '[' . $this->printList($node->values, ', ') . ']'; case $node instanceof NameNode: @@ -332,6 +337,7 @@ protected function p(?Node $node, bool $isDescription = false): string return 'null'; case $node instanceof ObjectFieldNode: + case $node instanceof ConstObjectFieldNode: return $this->p($node->name) . ': ' . $this->p($node->value); case $node instanceof ObjectTypeDefinitionNode: @@ -359,6 +365,8 @@ protected function p(?Node $node, bool $isDescription = false): string ); case $node instanceof ObjectValueNode: + case $node instanceof ConstObjectValueNode: + // @phpstan-ignore-next-line weird generic issue return "{ {$this->printList($node->fields, ', ')} }"; case $node instanceof OperationDefinitionNode: @@ -513,7 +521,7 @@ protected function printListBlock(NodeList $list): string */ protected function addDescription(?StringValueNode $description, string $body): string { - return $this->join([$this->p($description, true), $body], "\n"); + return $this->join([$this->p($description), $body], "\n"); } /** diff --git a/src/Type/Definition/CustomScalarType.php b/src/Type/Definition/CustomScalarType.php index 1c0c0b421..fdcfc9fa1 100644 --- a/src/Type/Definition/CustomScalarType.php +++ b/src/Type/Definition/CustomScalarType.php @@ -4,6 +4,7 @@ use GraphQL\Error\Error; use GraphQL\Error\InvariantViolation; +use GraphQL\Language\AST\ConstValueNode; use GraphQL\Language\AST\Node; use GraphQL\Language\AST\ScalarTypeDefinitionNode; use GraphQL\Language\AST\ScalarTypeExtensionNode; @@ -12,12 +13,13 @@ use GraphQL\Utils\Utils; /** + * @phpstan-type ParseLiteralFn callable((ValueNode&Node)|(ConstValueNode&Node), array|null): mixed * @phpstan-type InputCustomScalarConfig array{ * name?: string|null, * description?: string|null, * serialize?: callable(mixed): mixed, * parseValue: callable(mixed): mixed, - * parseLiteral: callable(ValueNode&Node, array|null): mixed, + * parseLiteral: ParseLiteralFn, * astNode?: ScalarTypeDefinitionNode|null, * extensionASTNodes?: array|null * } @@ -26,7 +28,7 @@ * description?: string|null, * serialize: callable(mixed): mixed, * parseValue?: callable(mixed): mixed, - * parseLiteral?: callable(ValueNode&Node, array|null): mixed, + * parseLiteral?: ParseLiteralFn, * astNode?: ScalarTypeDefinitionNode|null, * extensionASTNodes?: array|null * } diff --git a/src/Type/Definition/LeafType.php b/src/Type/Definition/LeafType.php index 8fd36314e..db48234cc 100644 --- a/src/Type/Definition/LeafType.php +++ b/src/Type/Definition/LeafType.php @@ -4,15 +4,15 @@ use GraphQL\Error\Error; use GraphQL\Error\SerializationError; +use GraphQL\Language\AST\ConstValueNode; use GraphQL\Language\AST\Node; use GraphQL\Language\AST\ValueNode; -/* +/** export type GraphQLLeafType = GraphQLScalarType | GraphQLEnumType; -*/ - + */ interface LeafType { /** @@ -46,7 +46,7 @@ public function parseValue($value); * * Should throw an exception with a client friendly message on invalid value nodes, @see ClientAware. * - * @param ValueNode&Node $valueNode + * @param (ValueNode&Node)|(ConstValueNode&Node) $valueNode * @param array|null $variables * * @throws Error diff --git a/src/Utils/AST.php b/src/Utils/AST.php index 2e844a871..7d87c9ade 100644 --- a/src/Utils/AST.php +++ b/src/Utils/AST.php @@ -6,6 +6,9 @@ use GraphQL\Error\InvariantViolation; use GraphQL\Error\SerializationError; use GraphQL\Language\AST\BooleanValueNode; +use GraphQL\Language\AST\ConstListValueNode; +use GraphQL\Language\AST\ConstObjectValueNode; +use GraphQL\Language\AST\ConstValueNode; use GraphQL\Language\AST\DefinitionNode; use GraphQL\Language\AST\DocumentNode; use GraphQL\Language\AST\EnumValueNode; @@ -35,7 +38,6 @@ use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\NullableType; -use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; /** @@ -298,7 +300,7 @@ public static function astFromValue($value, InputType $type): ?ValueNode * | Enum Value | Mixed | * | Null Value | null | * - * @param (ValueNode&Node)|null $valueNode + * @param (ValueNode&Node)|(ConstValueNode&Node)|null $valueNode * @param array|null $variables * * @throws \Exception @@ -307,7 +309,7 @@ public static function astFromValue($value, InputType $type): ?ValueNode * * @api */ - public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $variables = null) + public static function valueFromAST(?Node $valueNode, Type $type, ?array $variables = null) { $undefined = Utils::undefined(); @@ -348,7 +350,7 @@ public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $v if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); - if ($valueNode instanceof ListValueNode) { + if ($valueNode instanceof ListValueNode || $valueNode instanceof ConstListValueNode) { $coercedValues = []; $itemNodes = $valueNode->values; foreach ($itemNodes as $itemNode) { @@ -385,7 +387,7 @@ public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $v } if ($type instanceof InputObjectType) { - if (! $valueNode instanceof ObjectValueNode) { + if (! $valueNode instanceof ObjectValueNode && ! $valueNode instanceof ConstObjectValueNode) { // Invalid: intentionally return no value. return $undefined; } @@ -430,7 +432,10 @@ public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $v return $type->parseValue($coercedObj); } - if ($type instanceof EnumType) { + // Scalars and Enums fulfill parsing a literal value via parseLiteral(). + // Invalid values represent a failure to parse correctly, in which case + // no value is returned. + if ($type instanceof LeafType) { try { return $type->parseLiteral($valueNode, $variables); } catch (\Throwable $error) { @@ -438,26 +443,18 @@ public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $v } } - assert($type instanceof ScalarType, 'only remaining option'); - - // Scalars fulfill parsing a literal value via parseLiteral(). - // Invalid values represent a failure to parse correctly, in which case - // no value is returned. - try { - return $type->parseLiteral($valueNode, $variables); - } catch (\Throwable $error) { - return $undefined; - } + $unexpectedInputType = Utils::printSafe($type); + throw new \Exception("Unexpected input type: {$unexpectedInputType}"); } /** * Returns true if the provided valueNode is a variable which is not defined * in the set of variables. * - * @param ValueNode&Node $valueNode + * @param (ValueNode&Node)|(ConstValueNode&Node) $valueNode * @param array|null $variables */ - private static function isMissingVariable(ValueNode $valueNode, ?array $variables): bool + private static function isMissingVariable(Node $valueNode, ?array $variables): bool { return $valueNode instanceof VariableNode && ($variables === null || ! \array_key_exists($valueNode->name->value, $variables)); @@ -505,6 +502,7 @@ public static function valueFromASTUntyped(Node $valueNode, ?array $variables = return $valueNode->value; case $valueNode instanceof ListValueNode: + case $valueNode instanceof ConstListValueNode: $values = []; foreach ($valueNode->values as $node) { $values[] = self::valueFromASTUntyped($node, $variables); @@ -513,6 +511,7 @@ public static function valueFromASTUntyped(Node $valueNode, ?array $variables = return $values; case $valueNode instanceof ObjectValueNode: + case $valueNode instanceof ConstObjectValueNode: $values = []; foreach ($valueNode->fields as $field) { $values[$field->name->value] = self::valueFromASTUntyped($field->value, $variables); diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 241a9471d..63ff27134 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -4,6 +4,8 @@ use GraphQL\Error\InvariantViolation; use GraphQL\Language\AST\ArgumentNode; +use GraphQL\Language\AST\ConstListValueNode; +use GraphQL\Language\AST\ConstObjectFieldNode; use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\EnumValueNode; use GraphQL\Language\AST\FieldNode; @@ -274,6 +276,7 @@ public function enter(Node $node): void break; case $node instanceof ListValueNode: + case $node instanceof ConstListValueNode: $type = $this->getInputType(); $listType = $type instanceof NonNull ? $type->getWrappedType() @@ -287,6 +290,7 @@ public function enter(Node $node): void break; case $node instanceof ObjectFieldNode: + case $node instanceof ConstObjectFieldNode: $objectType = Type::getNamedType($this->getInputType()); $inputField = null; $inputFieldType = null; @@ -420,7 +424,9 @@ public function leave(Node $node): void \array_pop($this->inputTypeStack); break; case $node instanceof ListValueNode: + case $node instanceof ConstListValueNode: case $node instanceof ObjectFieldNode: + case $node instanceof ConstObjectFieldNode: \array_pop($this->defaultValueStack); \array_pop($this->inputTypeStack); break; diff --git a/src/Validator/Rules/UniqueInputFieldNames.php b/src/Validator/Rules/UniqueInputFieldNames.php index 88a50e22b..8f3ff5856 100644 --- a/src/Validator/Rules/UniqueInputFieldNames.php +++ b/src/Validator/Rules/UniqueInputFieldNames.php @@ -3,6 +3,7 @@ namespace GraphQL\Validator\Rules; use GraphQL\Error\Error; +use GraphQL\Language\AST\ConstObjectFieldNode; use GraphQL\Language\AST\NameNode; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\ObjectFieldNode; @@ -54,7 +55,8 @@ public function getASTVisitor(ValidationContext $context): array $this->knownNames = $knownNames; }, ], - NodeKind::OBJECT_FIELD => function (ObjectFieldNode $node) use ($context): VisitorOperation { + NodeKind::OBJECT_FIELD => function ($node) use ($context): VisitorOperation { + assert($node instanceof ObjectFieldNode || $node instanceof ConstObjectFieldNode); $fieldName = $node->name->value; if (isset($this->knownNames[$fieldName])) { diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index 4e83f3b3e..7f64fba74 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -4,17 +4,21 @@ use GraphQL\Error\Error; use GraphQL\Language\AST\BooleanValueNode; +use GraphQL\Language\AST\ConstListValueNode; +use GraphQL\Language\AST\ConstObjectFieldNode; +use GraphQL\Language\AST\ConstObjectValueNode; +use GraphQL\Language\AST\ConstValueNode; use GraphQL\Language\AST\EnumValueNode; use GraphQL\Language\AST\FloatValueNode; use GraphQL\Language\AST\IntValueNode; use GraphQL\Language\AST\ListValueNode; +use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NullValueNode; use GraphQL\Language\AST\ObjectFieldNode; use GraphQL\Language\AST\ObjectValueNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Language\AST\ValueNode; -use GraphQL\Language\AST\VariableNode; use GraphQL\Language\Printer; use GraphQL\Language\Visitor; use GraphQL\Language\VisitorOperation; @@ -31,6 +35,9 @@ * * A GraphQL document is only valid if all value literals are of the type * expected at their position. + * + * @phpstan-import-type ValueNodeVariants from ValueNode + * @phpstan-import-type ConstValueNodeVariants from ConstValueNode */ class ValuesOfCorrectType extends ValidationRule { @@ -50,7 +57,8 @@ public function getVisitor(QueryValidationContext $context): array ); } }, - NodeKind::LST => function (ListValueNode $node) use ($context): ?VisitorOperation { + NodeKind::LST => function ($node) use ($context): ?VisitorOperation { + assert($node instanceof ListValueNode || $node instanceof ConstListValueNode); // Note: TypeInfo will traverse into a list's item type, so look to the // parent input type to check if it is a list. $parentType = $context->getParentInputType(); @@ -65,7 +73,8 @@ public function getVisitor(QueryValidationContext $context): array return null; }, - NodeKind::OBJECT => function (ObjectValueNode $node) use ($context): ?VisitorOperation { + NodeKind::OBJECT => function ($node) use ($context): ?VisitorOperation { + assert($node instanceof ObjectValueNode || $node instanceof ConstObjectValueNode); $type = Type::getNamedType($context->getInputType()); if (! $type instanceof InputObjectType) { $this->isValidValueNode($context, $node); @@ -95,7 +104,8 @@ public function getVisitor(QueryValidationContext $context): array return null; }, - NodeKind::OBJECT_FIELD => static function (ObjectFieldNode $node) use ($context): void { + NodeKind::OBJECT_FIELD => static function ($node) use ($context): void { + assert($node instanceof ObjectFieldNode || $node instanceof ConstObjectFieldNode); $parentType = Type::getNamedType($context->getParentInputType()); if (! $parentType instanceof InputObjectType) { return; @@ -139,9 +149,9 @@ public function getVisitor(QueryValidationContext $context): array } /** - * @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $node + * @param ValueNodeVariants|ConstValueNodeVariants $node */ - protected function isValidValueNode(QueryValidationContext $context, ValueNode $node): void + protected function isValidValueNode(QueryValidationContext $context, Node $node): void { // Report any error at the full type expected by the location. $locationType = $context->getInputType(); diff --git a/src/Validator/Rules/VariablesInAllowedPosition.php b/src/Validator/Rules/VariablesInAllowedPosition.php index 9c9a95231..332e4db22 100644 --- a/src/Validator/Rules/VariablesInAllowedPosition.php +++ b/src/Validator/Rules/VariablesInAllowedPosition.php @@ -4,10 +4,10 @@ use GraphQL\Error\Error; use GraphQL\Error\InvariantViolation; +use GraphQL\Language\AST\ConstValueNode; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NullValueNode; use GraphQL\Language\AST\OperationDefinitionNode; -use GraphQL\Language\AST\ValueNode; use GraphQL\Language\AST\VariableDefinitionNode; use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\Type; @@ -86,12 +86,11 @@ public static function badVarPosMessage(string $varName, string $varType, string * which includes considering if default values exist for either the variable * or the location at which it is located. * - * @param ValueNode|null $varDefaultValue - * @param mixed $locationDefaultValue + * @param mixed $locationDefaultValue * * @throws InvariantViolation */ - protected function allowedVariableUsage(Schema $schema, Type $varType, $varDefaultValue, Type $locationType, $locationDefaultValue): bool + protected function allowedVariableUsage(Schema $schema, Type $varType, ?ConstValueNode $varDefaultValue, Type $locationType, $locationDefaultValue): bool { if ($locationType instanceof NonNull && ! $varType instanceof NonNull) { $hasNonNullVariableDefaultValue = $varDefaultValue !== null && ! $varDefaultValue instanceof NullValueNode; diff --git a/tests/Executor/VariablesTest.php b/tests/Executor/VariablesTest.php index 7870ce611..51c509bb9 100644 --- a/tests/Executor/VariablesTest.php +++ b/tests/Executor/VariablesTest.php @@ -225,6 +225,9 @@ private function fieldWithInputArg(array $inputArg): array ]; } + /** + * describe('using variables', () => {. + */ public function testUsingVariables(): void { $doc = ' @@ -233,7 +236,7 @@ public function testUsingVariables(): void } '; - // executes with complex input: + // it('executes with complex input', () => { $params = ['input' => ['a' => 'foo', 'b' => ['bar'], 'c' => 'baz']]; $result = $this->executeQuery($doc, $params);